diff options
Diffstat (limited to 'ssh-pkcs11-helper.c')
-rw-r--r-- | ssh-pkcs11-helper.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/ssh-pkcs11-helper.c b/ssh-pkcs11-helper.c new file mode 100644 index 000000000..d3bfb9838 --- /dev/null +++ b/ssh-pkcs11-helper.c | |||
@@ -0,0 +1,372 @@ | |||
1 | /* $OpenBSD: ssh-pkcs11-helper.c,v 1.3 2010/02/24 06:12:53 djm Exp $ */ | ||
2 | /* | ||
3 | * Copyright (c) 2010 Markus Friedl. All rights reserved. | ||
4 | * | ||
5 | * Permission to use, copy, modify, and distribute this software for any | ||
6 | * purpose with or without fee is hereby granted, provided that the above | ||
7 | * copyright notice and this permission notice appear in all copies. | ||
8 | * | ||
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
16 | */ | ||
17 | |||
18 | #include "includes.h" | ||
19 | |||
20 | #ifdef ENABLE_PKCS11 | ||
21 | |||
22 | #include <sys/types.h> | ||
23 | #ifdef HAVE_SYS_TIME_H | ||
24 | # include <sys/time.h> | ||
25 | #endif | ||
26 | |||
27 | #include "openbsd-compat/sys-queue.h" | ||
28 | |||
29 | #include <stdarg.h> | ||
30 | #include <string.h> | ||
31 | #include <unistd.h> | ||
32 | #include <errno.h> | ||
33 | |||
34 | #include "xmalloc.h" | ||
35 | #include "buffer.h" | ||
36 | #include "log.h" | ||
37 | #include "misc.h" | ||
38 | #include "key.h" | ||
39 | #include "authfd.h" | ||
40 | #include "ssh-pkcs11.h" | ||
41 | |||
42 | /* borrows code from sftp-server and ssh-agent */ | ||
43 | |||
44 | struct pkcs11_keyinfo { | ||
45 | Key *key; | ||
46 | char *providername; | ||
47 | TAILQ_ENTRY(pkcs11_keyinfo) next; | ||
48 | }; | ||
49 | |||
50 | TAILQ_HEAD(, pkcs11_keyinfo) pkcs11_keylist; | ||
51 | |||
52 | #define MAX_MSG_LENGTH 10240 /*XXX*/ | ||
53 | |||
54 | /* helper */ | ||
55 | #define get_int() buffer_get_int(&iqueue); | ||
56 | #define get_string(lenp) buffer_get_string(&iqueue, lenp); | ||
57 | |||
58 | /* input and output queue */ | ||
59 | Buffer iqueue; | ||
60 | Buffer oqueue; | ||
61 | |||
62 | static void | ||
63 | add_key(Key *k, char *name) | ||
64 | { | ||
65 | struct pkcs11_keyinfo *ki; | ||
66 | |||
67 | ki = xcalloc(1, sizeof(*ki)); | ||
68 | ki->providername = xstrdup(name); | ||
69 | ki->key = k; | ||
70 | TAILQ_INSERT_TAIL(&pkcs11_keylist, ki, next); | ||
71 | } | ||
72 | |||
73 | static void | ||
74 | del_keys_by_name(char *name) | ||
75 | { | ||
76 | struct pkcs11_keyinfo *ki, *nxt; | ||
77 | |||
78 | for (ki = TAILQ_FIRST(&pkcs11_keylist); ki; ki = nxt) { | ||
79 | nxt = TAILQ_NEXT(ki, next); | ||
80 | if (!strcmp(ki->providername, name)) { | ||
81 | TAILQ_REMOVE(&pkcs11_keylist, ki, next); | ||
82 | xfree(ki->providername); | ||
83 | key_free(ki->key); | ||
84 | free(ki); | ||
85 | } | ||
86 | } | ||
87 | } | ||
88 | |||
89 | /* lookup matching 'private' key */ | ||
90 | static Key * | ||
91 | lookup_key(Key *k) | ||
92 | { | ||
93 | struct pkcs11_keyinfo *ki; | ||
94 | |||
95 | TAILQ_FOREACH(ki, &pkcs11_keylist, next) { | ||
96 | debug("check %p %s", ki, ki->providername); | ||
97 | if (key_equal(k, ki->key)) | ||
98 | return (ki->key); | ||
99 | } | ||
100 | return (NULL); | ||
101 | } | ||
102 | |||
103 | static void | ||
104 | send_msg(Buffer *m) | ||
105 | { | ||
106 | int mlen = buffer_len(m); | ||
107 | |||
108 | buffer_put_int(&oqueue, mlen); | ||
109 | buffer_append(&oqueue, buffer_ptr(m), mlen); | ||
110 | buffer_consume(m, mlen); | ||
111 | } | ||
112 | |||
113 | static void | ||
114 | process_add(void) | ||
115 | { | ||
116 | char *name, *pin; | ||
117 | Key **keys; | ||
118 | int i, nkeys; | ||
119 | u_char *blob; | ||
120 | u_int blen; | ||
121 | Buffer msg; | ||
122 | |||
123 | buffer_init(&msg); | ||
124 | name = get_string(NULL); | ||
125 | pin = get_string(NULL); | ||
126 | if ((nkeys = pkcs11_add_provider(name, pin, &keys)) > 0) { | ||
127 | buffer_put_char(&msg, SSH2_AGENT_IDENTITIES_ANSWER); | ||
128 | buffer_put_int(&msg, nkeys); | ||
129 | for (i = 0; i < nkeys; i++) { | ||
130 | key_to_blob(keys[i], &blob, &blen); | ||
131 | buffer_put_string(&msg, blob, blen); | ||
132 | buffer_put_cstring(&msg, name); | ||
133 | xfree(blob); | ||
134 | add_key(keys[i], name); | ||
135 | } | ||
136 | xfree(keys); | ||
137 | } else { | ||
138 | buffer_put_char(&msg, SSH_AGENT_FAILURE); | ||
139 | } | ||
140 | xfree(pin); | ||
141 | xfree(name); | ||
142 | send_msg(&msg); | ||
143 | buffer_free(&msg); | ||
144 | } | ||
145 | |||
146 | static void | ||
147 | process_del(void) | ||
148 | { | ||
149 | char *name, *pin; | ||
150 | Buffer msg; | ||
151 | |||
152 | buffer_init(&msg); | ||
153 | name = get_string(NULL); | ||
154 | pin = get_string(NULL); | ||
155 | del_keys_by_name(name); | ||
156 | if (pkcs11_del_provider(name) == 0) | ||
157 | buffer_put_char(&msg, SSH_AGENT_SUCCESS); | ||
158 | else | ||
159 | buffer_put_char(&msg, SSH_AGENT_FAILURE); | ||
160 | xfree(pin); | ||
161 | xfree(name); | ||
162 | send_msg(&msg); | ||
163 | buffer_free(&msg); | ||
164 | } | ||
165 | |||
166 | static void | ||
167 | process_sign(void) | ||
168 | { | ||
169 | u_char *blob, *data, *signature = NULL; | ||
170 | u_int blen, dlen, slen = 0; | ||
171 | int ok = -1, flags, ret; | ||
172 | Key *key, *found; | ||
173 | Buffer msg; | ||
174 | |||
175 | blob = get_string(&blen); | ||
176 | data = get_string(&dlen); | ||
177 | flags = get_int(); /* XXX ignore */ | ||
178 | |||
179 | if ((key = key_from_blob(blob, blen)) != NULL) { | ||
180 | if ((found = lookup_key(key)) != NULL) { | ||
181 | slen = RSA_size(key->rsa); | ||
182 | signature = xmalloc(slen); | ||
183 | if ((ret = RSA_private_encrypt(dlen, data, signature, | ||
184 | found->rsa, RSA_PKCS1_PADDING)) != -1) { | ||
185 | slen = ret; | ||
186 | ok = 0; | ||
187 | } | ||
188 | } | ||
189 | key_free(key); | ||
190 | } | ||
191 | buffer_init(&msg); | ||
192 | if (ok == 0) { | ||
193 | buffer_put_char(&msg, SSH2_AGENT_SIGN_RESPONSE); | ||
194 | buffer_put_string(&msg, signature, slen); | ||
195 | } else { | ||
196 | buffer_put_char(&msg, SSH_AGENT_FAILURE); | ||
197 | } | ||
198 | xfree(data); | ||
199 | xfree(blob); | ||
200 | if (signature != NULL) | ||
201 | xfree(signature); | ||
202 | send_msg(&msg); | ||
203 | buffer_free(&msg); | ||
204 | } | ||
205 | |||
206 | static void | ||
207 | process(void) | ||
208 | { | ||
209 | u_int msg_len; | ||
210 | u_int buf_len; | ||
211 | u_int consumed; | ||
212 | u_int type; | ||
213 | u_char *cp; | ||
214 | |||
215 | buf_len = buffer_len(&iqueue); | ||
216 | if (buf_len < 5) | ||
217 | return; /* Incomplete message. */ | ||
218 | cp = buffer_ptr(&iqueue); | ||
219 | msg_len = get_u32(cp); | ||
220 | if (msg_len > MAX_MSG_LENGTH) { | ||
221 | error("bad message len %d", msg_len); | ||
222 | cleanup_exit(11); | ||
223 | } | ||
224 | if (buf_len < msg_len + 4) | ||
225 | return; | ||
226 | buffer_consume(&iqueue, 4); | ||
227 | buf_len -= 4; | ||
228 | type = buffer_get_char(&iqueue); | ||
229 | switch (type) { | ||
230 | case SSH_AGENTC_ADD_SMARTCARD_KEY: | ||
231 | debug("process_add"); | ||
232 | process_add(); | ||
233 | break; | ||
234 | case SSH_AGENTC_REMOVE_SMARTCARD_KEY: | ||
235 | debug("process_del"); | ||
236 | process_del(); | ||
237 | break; | ||
238 | case SSH2_AGENTC_SIGN_REQUEST: | ||
239 | debug("process_sign"); | ||
240 | process_sign(); | ||
241 | break; | ||
242 | default: | ||
243 | error("Unknown message %d", type); | ||
244 | break; | ||
245 | } | ||
246 | /* discard the remaining bytes from the current packet */ | ||
247 | if (buf_len < buffer_len(&iqueue)) { | ||
248 | error("iqueue grew unexpectedly"); | ||
249 | cleanup_exit(255); | ||
250 | } | ||
251 | consumed = buf_len - buffer_len(&iqueue); | ||
252 | if (msg_len < consumed) { | ||
253 | error("msg_len %d < consumed %d", msg_len, consumed); | ||
254 | cleanup_exit(255); | ||
255 | } | ||
256 | if (msg_len > consumed) | ||
257 | buffer_consume(&iqueue, msg_len - consumed); | ||
258 | } | ||
259 | |||
260 | void | ||
261 | cleanup_exit(int i) | ||
262 | { | ||
263 | /* XXX */ | ||
264 | _exit(i); | ||
265 | } | ||
266 | |||
267 | int | ||
268 | main(int argc, char **argv) | ||
269 | { | ||
270 | fd_set *rset, *wset; | ||
271 | int in, out, max, log_stderr = 0; | ||
272 | ssize_t len, olen, set_size; | ||
273 | SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; | ||
274 | LogLevel log_level = SYSLOG_LEVEL_ERROR; | ||
275 | char buf[4*4096]; | ||
276 | |||
277 | extern char *optarg; | ||
278 | extern char *__progname; | ||
279 | |||
280 | TAILQ_INIT(&pkcs11_keylist); | ||
281 | pkcs11_init(0); | ||
282 | |||
283 | init_rng(); | ||
284 | seed_rng(); | ||
285 | __progname = ssh_get_progname(argv[0]); | ||
286 | |||
287 | log_init(__progname, log_level, log_facility, log_stderr); | ||
288 | |||
289 | in = STDIN_FILENO; | ||
290 | out = STDOUT_FILENO; | ||
291 | |||
292 | max = 0; | ||
293 | if (in > max) | ||
294 | max = in; | ||
295 | if (out > max) | ||
296 | max = out; | ||
297 | |||
298 | buffer_init(&iqueue); | ||
299 | buffer_init(&oqueue); | ||
300 | |||
301 | set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask); | ||
302 | rset = (fd_set *)xmalloc(set_size); | ||
303 | wset = (fd_set *)xmalloc(set_size); | ||
304 | |||
305 | for (;;) { | ||
306 | memset(rset, 0, set_size); | ||
307 | memset(wset, 0, set_size); | ||
308 | |||
309 | /* | ||
310 | * Ensure that we can read a full buffer and handle | ||
311 | * the worst-case length packet it can generate, | ||
312 | * otherwise apply backpressure by stopping reads. | ||
313 | */ | ||
314 | if (buffer_check_alloc(&iqueue, sizeof(buf)) && | ||
315 | buffer_check_alloc(&oqueue, MAX_MSG_LENGTH)) | ||
316 | FD_SET(in, rset); | ||
317 | |||
318 | olen = buffer_len(&oqueue); | ||
319 | if (olen > 0) | ||
320 | FD_SET(out, wset); | ||
321 | |||
322 | if (select(max+1, rset, wset, NULL, NULL) < 0) { | ||
323 | if (errno == EINTR) | ||
324 | continue; | ||
325 | error("select: %s", strerror(errno)); | ||
326 | cleanup_exit(2); | ||
327 | } | ||
328 | |||
329 | /* copy stdin to iqueue */ | ||
330 | if (FD_ISSET(in, rset)) { | ||
331 | len = read(in, buf, sizeof buf); | ||
332 | if (len == 0) { | ||
333 | debug("read eof"); | ||
334 | cleanup_exit(0); | ||
335 | } else if (len < 0) { | ||
336 | error("read: %s", strerror(errno)); | ||
337 | cleanup_exit(1); | ||
338 | } else { | ||
339 | buffer_append(&iqueue, buf, len); | ||
340 | } | ||
341 | } | ||
342 | /* send oqueue to stdout */ | ||
343 | if (FD_ISSET(out, wset)) { | ||
344 | len = write(out, buffer_ptr(&oqueue), olen); | ||
345 | if (len < 0) { | ||
346 | error("write: %s", strerror(errno)); | ||
347 | cleanup_exit(1); | ||
348 | } else { | ||
349 | buffer_consume(&oqueue, len); | ||
350 | } | ||
351 | } | ||
352 | |||
353 | /* | ||
354 | * Process requests from client if we can fit the results | ||
355 | * into the output buffer, otherwise stop processing input | ||
356 | * and let the output queue drain. | ||
357 | */ | ||
358 | if (buffer_check_alloc(&oqueue, MAX_MSG_LENGTH)) | ||
359 | process(); | ||
360 | } | ||
361 | } | ||
362 | #else /* ENABLE_PKCS11 */ | ||
363 | int | ||
364 | main(int argc, char **argv) | ||
365 | { | ||
366 | extern char *__progname; | ||
367 | |||
368 | __progname = ssh_get_progname(argv[0]); | ||
369 | log_init(__progname, SYSLOG_LEVEL_ERROR, SYSLOG_FACILITY_AUTH, 0); | ||
370 | fatal("PKCS#11 support disabled at compile time"); | ||
371 | } | ||
372 | #endif /* ENABLE_PKCS11 */ | ||