monotone

monotone Mtn Source Tree

Root/src/ssh_agent.cc

1// Copyright (C) 2007 Justin Patrin <papercrane@reversefold.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include "base.hh"
11#include <stdlib.h>
12
13#include <boost/shared_ptr.hpp>
14#include <botan/botan.h>
15#include <botan/rsa.h>
16#include <botan/numthry.h>
17
18#include "ssh_agent.hh"
19#include "sanity.hh"
20#include "netio.hh"
21#include "numeric_vocab.hh"
22#include "platform.hh"
23#include "key_store.hh"
24
25#ifdef WIN32
26#include "win32/ssh_agent_platform.hh"
27#else
28#include "unix/ssh_agent_platform.hh"
29#endif
30
31using std::string;
32using std::vector;
33
34using boost::shared_ptr;
35using boost::shared_dynamic_cast;
36
37using Botan::RSA_PublicKey;
38using Botan::RSA_PrivateKey;
39using Botan::BigInt;
40using Botan::SecureVector;
41using Botan::X509_PublicKey;
42
43struct ssh_agent_state : ssh_agent_platform
44{
45 vector<RSA_PublicKey> keys; // cache
46
47 void read_packet(string & packet);
48 void write_packet(string const & packet);
49};
50
51/*
52 * The ssh-agent network format is essentially based on a u32 which
53 * is the length of the packet followed by that number of bytes.
54 *
55 * u32 encoding is big-endian
56 *
57 * The packet to ask for the keys that ssh-agent has is in this format:
58 * u32 = 1
59 * command = 11
60 *
61 * The response packet:
62 * u32 = length
63 * data
64 * byte = packet type (12)
65 * u32 = number of keys
66 * u32 = length of key
67 * data
68 * u32 = length of type
69 * data = string, the type of key (ssh-rsa, ssh-dss)
70 * if(rsa)
71 * u32 = length of 'e'
72 * data = binary encoded BigInt, 'e'
73 * u32 = length of 'n'
74 * data = binary encoded BigInt, 'n'
75 * if(dss)
76 * u32 = length of 'p'
77 * data = binary encoded BigInt, 'p'
78 * u32 = length of 'q'
79 * data = binary encoded BigInt, 'q'
80 * u32 = length of 'g'
81 * data = binary encoded BigInt, 'g'
82 * u32 = length of 'pub_key'
83 * data = binary encoded BigInt, 'pub_key'
84 * u32 = length of comment
85 * data = comment (path to key file)
86 * (repeat for number of keys)
87 *
88 * To ask for ssh-agent to sign data use this packet format:
89 * byte = packet type (13)
90 * u32 = length of data
91 * data
92 * u32 = length of key data
93 * key data
94 * (rsa)
95 * u32 = length of type
96 * data = type (ssh-rsa)
97 * u32 = length of 'e'
98 * data = binary encoded BigInt, 'e'
99 * u32 = length of 'n'
100 * data = binary encoded BigInt, 'n'
101 * (dss)
102 * NOT IMPLEMENTED, should be same as above
103 * u32 = length of data to sign
104 * data to sign
105 * u32 = flags (0)
106 *
107 * Response packet for signing request is:
108 * u32 = length of packet
109 * data
110 * byte = packet type (14)
111 * u32 = signature length
112 * data = signature
113 * u32 = type length
114 * data = type (ssh-rsa)
115 * u32 = signed data length
116 * data = signed data
117 */
118
119//
120// Helper functions for packing and unpacking data from the wire protocol.
121//
122
123static u32
124get_long(char const * buf)
125{
126 L((FL("ssh_agent: get_long: %u %u %u %u")
127 % widen<u32,char>(buf[0])
128 % widen<u32,char>(buf[1])
129 % widen<u32,char>(buf[2])
130 % widen<u32,char>(buf[3])));
131 return ((widen<u32,char>(buf[0]) << 24)
132 | (widen<u32,char>(buf[1]) << 16)
133 | (widen<u32,char>(buf[2]) << 8)
134 | widen<u32,char>(buf[3]));
135}
136
137static u32
138get_long_from_buf(string const & buf, u32 & loc)
139{
140 E(buf.length() >= loc + 4, origin::system,
141 F("string not long enough to get a long"));
142 u32 ret = get_long(buf.data() + loc);
143 loc += 4;
144 return ret;
145}
146
147static void
148get_string_from_buf(string const & buf,
149 u32 & loc,
150 u32 & len,
151 string & out)
152{
153 L(FL("ssh_agent: get_string_from_buf: buf length: %u, loc: %u" )
154 % buf.length()
155 % loc);
156 len = get_long_from_buf(buf, loc);
157 L(FL("ssh_agent: get_string_from_buf: len: %u" ) % len);
158 E(loc + len <= buf.length(), origin::system,
159 F("ssh_agent: length (%i) of buf less than loc (%u) + len (%u)")
160 % buf.length()
161 % loc
162 % len);
163 out = buf.substr(loc, len);
164 L(FL("ssh_agent: get_string_from_buf: out length: %u") % out.length());
165 loc += len;
166}
167
168static void
169put_long(u32 l, char * buf)
170{
171 buf[0] = (char)(unsigned char)(l >> 24);
172 buf[1] = (char)(unsigned char)(l >> 16);
173 buf[2] = (char)(unsigned char)(l >> 8);
174 buf[3] = (char)(unsigned char)(l);
175 L(FL("ssh_agent: long_to_buf: %u %u %u %u")
176 % (u32)(unsigned char)buf[0]
177 % (u32)(unsigned char)buf[1]
178 % (u32)(unsigned char)buf[2]
179 % (u32)(unsigned char)buf[3]);
180}
181
182static void
183put_long_into_buf(u32 l, string & buf)
184{
185 char lb[4];
186 L(FL("ssh_agent: put_long_into_buf: long: %u, buf len: %i")
187 % l
188 % buf.length());
189 put_long(l, lb);
190 buf.append(lb, 4);
191 L(FL("ssh_agent: put_long_into_buf: buf len now %i") % buf.length());
192}
193
194static void
195put_string_into_buf(string const & str, string & buf)
196{
197 L(FL("ssh_agent: put_string_into_buf: str len %i, buf len %i")
198 % str.length()
199 % buf.length());
200 put_long_into_buf(str.length(), buf);
201 buf.append(str.c_str(), str.length());
202 L(FL("ssh_agent: put_string_into_buf: buf len now %i") % buf.length());
203}
204
205static void
206put_bigint_into_buf(BigInt const & bi, string & buf)
207{
208 L(FL("ssh_agent: put_bigint_into_buf: bigint.bytes(): %u, bigint: %s")
209 % bi.bytes()
210 % bi);
211 SecureVector<Botan::byte> bi_buf = BigInt::encode(bi);
212 string bi_str;
213 if (*bi_buf.begin() & 0x80)
214 bi_str.append(1, static_cast<char>(0));
215 bi_str.append((char *) bi_buf.begin(), bi_buf.size());
216 put_string_into_buf(bi_str, buf);
217 L(FL("ssh_agent: put_bigint_into_buf: buf len now %i") % buf.length());
218}
219
220static void
221put_public_key_into_buf(RSA_PublicKey const & key, string & buf)
222{
223 L(FL("ssh_agent: put_public_key_into_buf: key e: %s, n: %s")
224 % key.get_e()
225 % key.get_n());
226 put_string_into_buf("ssh-rsa", buf);
227 put_bigint_into_buf(key.get_e(), buf);
228 put_bigint_into_buf(key.get_n(), buf);
229 L(FL("ssh_agent: put_public_key_into_buf: buf len now %i") % buf.length());
230}
231
232static void
233put_private_key_into_buf(RSA_PrivateKey const & key, string & buf)
234{
235 L(FL("ssh_agent: put_private_key_into_buf: key e: %s, n: %s")
236 % key.get_e()
237 % key.get_n());
238 put_string_into_buf("ssh-rsa", buf);
239 put_bigint_into_buf(key.get_n(), buf);
240 put_bigint_into_buf(key.get_e(), buf);
241 put_bigint_into_buf(key.get_d(), buf);
242 BigInt iqmp = inverse_mod(key.get_q(), key.get_p());
243 put_bigint_into_buf(iqmp, buf);
244 put_bigint_into_buf(key.get_p(), buf);
245 put_bigint_into_buf(key.get_q(), buf);
246 L(FL("ssh_agent: put_private_key_into_buf: buf len now %i") % buf.length());
247}
248
249//
250// Non-platform-specific ssh_agent_state methods, dealing with the basic
251// protocol packet format.
252//
253
254void
255ssh_agent_state::read_packet(string & packet)
256{
257 u32 len;
258 string len_buf;
259 read_data(4, len_buf);
260 u32 l = 0;
261 len = get_long_from_buf(len_buf, l);
262 E(len > 0, origin::system,
263 F("ssh_agent: fetch_packet: zero-length packet from ssh-agent"));
264
265 L(FL("ssh_agent: fetch_packet: response len %u") % len);
266
267 read_data(len, packet);
268}
269
270void
271ssh_agent_state::write_packet(string const & packet)
272{
273 string sized_packet;
274 put_string_into_buf(packet, sized_packet);
275 write_data(sized_packet);
276}
277
278//
279// ssh_agent public methods.
280//
281
282ssh_agent::ssh_agent()
283 : s(new ssh_agent_state)
284{
285}
286
287ssh_agent::~ssh_agent()
288{}
289
290bool
291ssh_agent::connected()
292{
293 return s->connected();
294}
295
296vector<RSA_PublicKey> const
297ssh_agent::get_keys()
298{
299 if (!s->connected())
300 {
301 L(FL("ssh_agent: get_keys: stream not initialized, no agent"));
302 return s->keys;
303 }
304
305 const char get_keys_cmd[1] = { 11 };
306 s->write_packet(string(get_keys_cmd, sizeof get_keys_cmd));
307
308 string packet;
309 s->read_packet(packet);
310
311 //first byte is packet type
312 u32 packet_loc = 0;
313 E(packet.at(0) == 12, origin::system,
314 F("ssh_agent: packet type (%u) != 12")
315 % (u32)packet.at(0));
316 packet_loc += 1;
317
318 u32 num_keys = get_long_from_buf(packet, packet_loc);
319 L(FL("ssh_agent: %u keys") % num_keys);
320
321 for (u32 key_num = 0; key_num < num_keys; ++key_num)
322 {
323 L(FL("ssh_agent: getting key # %u") % key_num);
324
325 u32 key_len;
326 string key;
327 get_string_from_buf(packet, packet_loc, key_len, key);
328
329 u32 key_loc = 0, slen;
330 string type;
331 get_string_from_buf(key, key_loc, slen, type);
332
333 L(FL("ssh_agent: type: %s") % type);
334
335 if (type == "ssh-rsa")
336 {
337 L(FL("ssh_agent: RSA"));
338 string e_str;
339 get_string_from_buf(key, key_loc, slen, e_str);
340 BigInt e = BigInt::decode((unsigned char *)(e_str.c_str()),
341 e_str.length(),
342 BigInt::Binary);
343 L(FL("ssh_agent: e: %s, len %u") % e % slen);
344 string n_str;
345 get_string_from_buf(key, key_loc, slen, n_str);
346 BigInt n = BigInt::decode((unsigned char *)(n_str.c_str()),
347 n_str.length(),
348 BigInt::Binary);
349 L(FL("ssh_agent: n: %s, len %u") % n % slen);
350
351 E(key.length() == key_loc, origin::system,
352 F("ssh_agent: get_keys: not all or too many key bytes consumed,"
353 " location (%u), length (%i)")
354 % key_loc
355 % key.length());
356
357 RSA_PublicKey rsa_key(n, e);
358 s->keys.push_back(rsa_key);
359 }
360 else
361 L(FL("ssh_agent: ignoring key of type '%s'") % type);
362
363 L(FL("ssh_agent: packet length %u, packet loc %u, key length %u,"
364 " key loc, %u")
365 % packet.length()
366 % packet_loc
367 % key.length()
368 % key_loc);
369
370 string comment;
371 u32 comment_len;
372 get_string_from_buf(packet, packet_loc, comment_len, comment);
373 L(FL("ssh_agent: comment_len: %u, comment: %s") % comment_len % comment);
374 }
375 E(packet.length() == packet_loc, origin::system,
376 F("ssh_agent: get_keys: not all or too many packet bytes consumed,"
377 " location (%u), length (%i)")
378 % packet_loc
379 % packet.length());
380
381 return s->keys;
382}
383
384bool
385ssh_agent::has_key(const keypair & key)
386{
387 //grab the monotone public key as an RSA_PublicKey
388 SecureVector<Botan::byte> pub_block
389 (reinterpret_cast<Botan::byte const *>((key.pub)().data()),
390 (key.pub)().size());
391 L(FL("has_key: building %d-byte pub key") % pub_block.size());
392 shared_ptr<X509_PublicKey> x509_key =
393 shared_ptr<X509_PublicKey>(Botan::X509::load_key(pub_block));
394 shared_ptr<RSA_PublicKey> pub_key = shared_dynamic_cast<RSA_PublicKey>(x509_key);
395
396 if (!pub_key)
397 throw recoverable_failure(origin::system,
398 "has_key: Failed to get monotone RSA public key");
399
400 vector<RSA_PublicKey> ssh_keys = get_keys();
401 for (vector<RSA_PublicKey>::const_iterator
402 si = ssh_keys.begin(); si != ssh_keys.end(); ++si)
403 {
404 if ((*pub_key).get_e() == (*si).get_e()
405 && (*pub_key).get_n() == (*si).get_n())
406 {
407 L(FL("has_key: key found"));
408 return true;
409 }
410 }
411 return false;
412}
413
414void
415ssh_agent::sign_data(RSA_PublicKey const & key,
416 string const & data,
417 string & out)
418{
419 E(connected(), origin::system,
420 F("ssh_agent: get_keys: attempted to sign data when not connected"));
421
422 L(FL("ssh_agent: sign_data: key e: %s, n: %s, data len: %i")
423 % key.get_e()
424 % key.get_n()
425 % data.length());
426 string packet_out;
427 string key_buf;
428 string full_sig;
429
430 packet_out.append(1, (char)13); // command byte
431 put_public_key_into_buf(key, key_buf);
432 put_string_into_buf(key_buf, packet_out);
433
434 put_string_into_buf(data, packet_out);
435 u32 flags = 0;
436 put_long_into_buf(flags, packet_out);
437
438 L(FL("ssh_agent: sign_data: data_out length: %u") % packet_out.length());
439 s->write_packet(packet_out);
440
441 string packet_in;
442 s->read_packet(packet_in);
443
444 u32 packet_in_loc = 0;
445 if (packet_in.at(0) != 14)
446 {
447 L(FL("ssh_agent: sign_data: packet_in type (%u) != 14")
448 % (u32)packet_in.at(0));
449 return;
450 }
451 packet_in_loc += 1;
452
453 u32 full_sig_len;
454 get_string_from_buf(packet_in, packet_in_loc, full_sig_len, full_sig);
455 L(FL("ssh_agent: sign_data: signed data length: %u (%u)")
456 % full_sig_len
457 % full_sig.length());
458
459 string type;
460 u32 full_sig_loc = 0, type_len, out_len;
461 get_string_from_buf(full_sig, full_sig_loc, type_len, type);
462 L(FL("ssh_agent: sign_data: type (%u), '%s'") % type_len % type);
463 get_string_from_buf(full_sig, full_sig_loc, out_len, out);
464 L(FL("ssh_agent: sign_data: output length %u") % out_len);
465 E(full_sig.length() == full_sig_loc, origin::system,
466 (F("ssh_agent: sign_data: not all or too many signature bytes consumed,"
467 " location (%u), length (%i)")
468 % full_sig_loc
469 % full_sig.length()));
470
471 E(packet_in.length() == packet_in_loc, origin::system,
472 (F("ssh_agent: sign_data: not all or too many packet bytes consumed,"
473 " location (%u), length (%i)")
474 % packet_in_loc
475 % packet_in.length()));
476}
477
478void
479ssh_agent::add_identity(RSA_PrivateKey const & key, string const & comment)
480{
481 E(s->connected(), origin::system,
482 F("ssh_agent: add_identity: attempted to add a key when not connected"));
483
484 L(FL("ssh_agent: add_identity: key e: %s, n: %s, comment len: %i")
485 % key.get_e()
486 % key.get_n()
487 % comment.length());
488 string packet_out;
489
490 packet_out.append(1, (char)17); // command byte
491 put_private_key_into_buf(key, packet_out);
492 put_string_into_buf(comment, packet_out);
493 s->write_packet(packet_out);
494
495 string packet_in;
496 s->read_packet(packet_in);
497 E(packet_in.length() == 1, origin::system,
498 F("ssh_agent: add_identity: response packet of unexpected size (%u)")
499 % packet_in.length());
500 E(packet_in.at(0) == 6, origin::system,
501 F("ssh_agent: packet type (%u) != 6")
502 % (u32)packet_in.at(0));
503}
504
505// Local Variables:
506// mode: C++
507// fill-column: 76
508// c-file-style: "gnu"
509// indent-tabs-mode: nil
510// End:
511// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status