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