agent: Keep CAP_DAC_OVERRIDE to connect to ssh-agent socket
[strongswan.git] / src / libstrongswan / plugins / agent / agent_private_key.c
1 /*
2 * Copyright (C) 2008-2009 Martin Willi
3 * Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "agent_private_key.h"
17
18 #include <stdlib.h>
19 #include <unistd.h>
20 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <sys/un.h>
23 #include <arpa/inet.h>
24 #include <errno.h>
25
26 #include <library.h>
27 #include <utils/chunk.h>
28 #include <utils/debug.h>
29
30 #ifndef UNIX_PATH_MAX
31 #define UNIX_PATH_MAX 108
32 #endif /* UNIX_PATH_MAX */
33
34 typedef struct private_agent_private_key_t private_agent_private_key_t;
35 typedef enum agent_msg_type_t agent_msg_type_t;
36
37 /**
38 * Private data of a agent_private_key_t object.
39 */
40 struct private_agent_private_key_t {
41 /**
42 * Public interface for this signer.
43 */
44 agent_private_key_t public;
45
46 /**
47 * ssh-agent unix socket connection
48 */
49 int socket;
50
51 /**
52 * public key encoded in SSH format
53 */
54 chunk_t key;
55
56 /**
57 * public key
58 */
59 public_key_t *pubkey;
60
61 /**
62 * keysize in bytes
63 */
64 size_t key_size;
65
66 /**
67 * reference count
68 */
69 refcount_t ref;
70 };
71
72 /**
73 * Message types for ssh-agent protocol
74 */
75 enum agent_msg_type_t {
76 SSH_AGENT_FAILURE = 5,
77 SSH_AGENT_SUCCESS = 6,
78 SSH_AGENT_ID_REQUEST = 11,
79 SSH_AGENT_ID_RESPONSE = 12,
80 SSH_AGENT_SIGN_REQUEST = 13,
81 SSH_AGENT_SIGN_RESPONSE = 14,
82 };
83
84 /**
85 * read a byte from a blob
86 */
87 static u_char read_byte(chunk_t *blob)
88 {
89 u_char val;
90
91 if (blob->len < sizeof(u_char))
92 {
93 return 0;
94 }
95 val = *(blob->ptr);
96 *blob = chunk_skip(*blob, sizeof(u_char));
97 return val;
98 }
99
100 /**
101 * read a u_int32_t from a blob
102 */
103 static u_int32_t read_uint32(chunk_t *blob)
104 {
105 u_int32_t val;
106
107 if (blob->len < sizeof(u_int32_t))
108 {
109 return 0;
110 }
111 val = ntohl(*(u_int32_t*)blob->ptr);
112 *blob = chunk_skip(*blob, sizeof(u_int32_t));
113 return val;
114 }
115
116 /**
117 * read a ssh-agent "string" length/value from a blob
118 */
119 static chunk_t read_string(chunk_t *blob)
120 {
121 int len;
122 chunk_t str;
123
124 len = read_uint32(blob);
125 if (len > blob->len)
126 {
127 return chunk_empty;
128 }
129 str = chunk_create(blob->ptr, len);
130 *blob = chunk_skip(*blob, + len);
131 return str;
132 }
133
134 /**
135 * open socket connection to the ssh-agent
136 */
137 static int open_connection(char *path)
138 {
139 struct sockaddr_un addr;
140 int s;
141
142 s = socket(AF_UNIX, SOCK_STREAM, 0);
143 if (s == -1)
144 {
145 DBG1(DBG_LIB, "opening ssh-agent socket %s failed: %s:", path,
146 strerror(errno));
147 return -1;
148 }
149
150 addr.sun_family = AF_UNIX;
151 addr.sun_path[UNIX_PATH_MAX - 1] = '\0';
152 strncpy(addr.sun_path, path, UNIX_PATH_MAX - 1);
153
154 if (connect(s, (struct sockaddr*)&addr, SUN_LEN(&addr)) != 0)
155 {
156 DBG1(DBG_LIB, "connecting to ssh-agent socket failed: %s",
157 strerror(errno));
158 close(s);
159 return -1;
160 }
161 return s;
162 }
163
164 /**
165 * Get the first usable key from the agent
166 */
167 static bool read_key(private_agent_private_key_t *this, public_key_t *pubkey)
168 {
169 int len;
170 char buf[2048];
171 chunk_t blob, key;
172
173 len = htonl(1);
174 buf[0] = SSH_AGENT_ID_REQUEST;
175 if (write(this->socket, &len, sizeof(len)) != sizeof(len) ||
176 write(this->socket, &buf, 1) != 1)
177 {
178 DBG1(DBG_LIB, "writing to ssh-agent failed");
179 return FALSE;
180 }
181
182 blob = chunk_create(buf, sizeof(buf));
183 blob.len = read(this->socket, blob.ptr, blob.len);
184
185 if (blob.len < sizeof(u_int32_t) + sizeof(u_char) ||
186 read_uint32(&blob) != blob.len ||
187 read_byte(&blob) != SSH_AGENT_ID_RESPONSE)
188 {
189 DBG1(DBG_LIB, "received invalid ssh-agent identity response");
190 return FALSE;
191 }
192 read_uint32(&blob);
193
194 while (blob.len)
195 {
196 key = read_string(&blob);
197 if (!key.len)
198 {
199 break;
200 }
201 this->pubkey = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_ANY,
202 BUILD_BLOB_SSHKEY, key, BUILD_END);
203 if (!this->pubkey)
204 {
205 continue;
206 }
207 if (pubkey && !private_key_belongs_to(&this->public.key, pubkey))
208 {
209 this->pubkey->destroy(this->pubkey);
210 this->pubkey = NULL;
211 continue;
212 }
213 this->key = chunk_clone(key);
214 return TRUE;
215 }
216 return FALSE;
217 }
218
219 static bool scheme_supported(private_agent_private_key_t *this,
220 signature_scheme_t scheme)
221 {
222 switch (this->pubkey->get_type(this->pubkey))
223 {
224 case KEY_RSA:
225 return scheme == SIGN_RSA_EMSA_PKCS1_SHA1;
226 case KEY_ECDSA:
227 return scheme == SIGN_ECDSA_256 ||
228 scheme == SIGN_ECDSA_384 ||
229 scheme == SIGN_ECDSA_521;
230 default:
231 return FALSE;
232 }
233 }
234
235 METHOD(private_key_t, sign, bool,
236 private_agent_private_key_t *this, signature_scheme_t scheme,
237 chunk_t data, chunk_t *signature)
238 {
239 u_int32_t len, flags;
240 char buf[2048];
241 chunk_t blob;
242
243 if (!scheme_supported(this, scheme))
244 {
245 DBG1(DBG_LIB, "signature scheme %N not supported by ssh-agent",
246 signature_scheme_names, scheme);
247 return FALSE;
248 }
249
250 len = htonl(1 + sizeof(u_int32_t) * 3 + this->key.len + data.len);
251 buf[0] = SSH_AGENT_SIGN_REQUEST;
252 if (write(this->socket, &len, sizeof(len)) != sizeof(len) ||
253 write(this->socket, &buf, 1) != 1)
254 {
255 DBG1(DBG_LIB, "writing to ssh-agent failed");
256 return FALSE;
257 }
258
259 len = htonl(this->key.len);
260 if (write(this->socket, &len, sizeof(len)) != sizeof(len) ||
261 write(this->socket, this->key.ptr, this->key.len) != this->key.len)
262 {
263 DBG1(DBG_LIB, "writing to ssh-agent failed");
264 return FALSE;
265 }
266
267 len = htonl(data.len);
268 if (write(this->socket, &len, sizeof(len)) != sizeof(len) ||
269 write(this->socket, data.ptr, data.len) != data.len)
270 {
271 DBG1(DBG_LIB, "writing to ssh-agent failed");
272 return FALSE;
273 }
274
275 flags = htonl(0);
276 if (write(this->socket, &flags, sizeof(flags)) != sizeof(flags))
277 {
278 DBG1(DBG_LIB, "writing to ssh-agent failed");
279 return FALSE;
280 }
281
282 blob = chunk_create(buf, sizeof(buf));
283 blob.len = read(this->socket, blob.ptr, blob.len);
284 if (blob.len < sizeof(u_int32_t) + sizeof(u_char) ||
285 read_uint32(&blob) != blob.len ||
286 read_byte(&blob) != SSH_AGENT_SIGN_RESPONSE)
287 {
288 DBG1(DBG_LIB, "received invalid ssh-agent signature response");
289 return FALSE;
290 }
291 /* parse length */
292 blob = read_string(&blob);
293 /* check sig type */
294 if (chunk_equals(read_string(&blob), chunk_from_str("ssh-rsa")))
295 { /* for RSA the signature has no special encoding */
296 blob = read_string(&blob);
297 if (blob.len)
298 {
299 *signature = chunk_clone(blob);
300 return TRUE;
301 }
302 }
303 else
304 { /* anything else is treated as ECSDA for now */
305 blob = read_string(&blob);
306 if (blob.len)
307 {
308 chunk_t r, s;
309
310 r = read_string(&blob);
311 s = read_string(&blob);
312 if (r.len && s.len)
313 {
314 *signature = chunk_cat("cc", r, s);
315 return TRUE;
316 }
317 }
318 }
319 DBG1(DBG_LIB, "received invalid ssh-agent signature response");
320 return FALSE;
321 }
322
323 METHOD(private_key_t, get_type, key_type_t,
324 private_agent_private_key_t *this)
325 {
326 return this->pubkey->get_type(this->pubkey);
327 }
328
329 METHOD(private_key_t, decrypt, bool,
330 private_agent_private_key_t *this, encryption_scheme_t scheme,
331 chunk_t crypto, chunk_t *plain)
332 {
333 DBG1(DBG_LIB, "private key decryption not supported by ssh-agent");
334 return FALSE;
335 }
336
337 METHOD(private_key_t, get_keysize, int,
338 private_agent_private_key_t *this)
339 {
340 return this->pubkey->get_keysize(this->pubkey);
341 }
342
343 METHOD(private_key_t, get_public_key, public_key_t*,
344 private_agent_private_key_t *this)
345 {
346 return this->pubkey->get_ref(this->pubkey);
347 }
348
349 METHOD(private_key_t, get_encoding, bool,
350 private_agent_private_key_t *this, cred_encoding_type_t type,
351 chunk_t *encoding)
352 {
353 return FALSE;
354 }
355
356 METHOD(private_key_t, get_fingerprint, bool,
357 private_agent_private_key_t *this, cred_encoding_type_t type, chunk_t *fp)
358 {
359 return this->pubkey->get_fingerprint(this->pubkey, type, fp);
360 }
361
362 METHOD(private_key_t, get_ref, private_key_t*,
363 private_agent_private_key_t *this)
364 {
365 ref_get(&this->ref);
366 return &this->public.key;
367 }
368
369 METHOD(private_key_t, destroy, void,
370 private_agent_private_key_t *this)
371 {
372 if (ref_put(&this->ref))
373 {
374 close(this->socket);
375 chunk_free(&this->key);
376 DESTROY_IF(this->pubkey);
377 free(this);
378 }
379 }
380
381 /**
382 * See header.
383 */
384 agent_private_key_t *agent_private_key_open(key_type_t type, va_list args)
385 {
386 private_agent_private_key_t *this;
387 public_key_t *pubkey = NULL;
388 char *path = NULL;
389
390 while (TRUE)
391 {
392 switch (va_arg(args, builder_part_t))
393 {
394 case BUILD_AGENT_SOCKET:
395 path = va_arg(args, char*);
396 continue;
397 case BUILD_PUBLIC_KEY:
398 pubkey = va_arg(args, public_key_t*);
399 continue;
400 case BUILD_END:
401 break;
402 default:
403 return NULL;
404 }
405 break;
406 }
407 if (!path)
408 {
409 return NULL;
410 }
411
412 INIT(this,
413 .public = {
414 .key = {
415 .get_type = _get_type,
416 .sign = _sign,
417 .decrypt = _decrypt,
418 .get_keysize = _get_keysize,
419 .get_public_key = _get_public_key,
420 .belongs_to = private_key_belongs_to,
421 .equals = private_key_equals,
422 .get_fingerprint = _get_fingerprint,
423 .has_fingerprint = private_key_has_fingerprint,
424 .get_encoding = _get_encoding,
425 .get_ref = _get_ref,
426 .destroy = _destroy,
427 },
428 },
429 .ref = 1,
430 );
431
432 this->socket = open_connection(path);
433 if (this->socket < 0)
434 {
435 free(this);
436 return NULL;
437 }
438 if (!read_key(this, pubkey))
439 {
440 destroy(this);
441 return NULL;
442 }
443 return &this->public;
444 }