diff options
-rw-r--r-- | ssh-sk-client.c | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/ssh-sk-client.c b/ssh-sk-client.c new file mode 100644 index 000000000..35e268472 --- /dev/null +++ b/ssh-sk-client.c | |||
@@ -0,0 +1,323 @@ | |||
1 | /* $OpenBSD: ssh-sk-client.c,v 1.1 2019/12/13 20:16:56 djm Exp $ */ | ||
2 | /* | ||
3 | * Copyright (c) 2019 Google LLC | ||
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 <sys/types.h> | ||
19 | #include <sys/socket.h> | ||
20 | #include <sys/wait.h> | ||
21 | |||
22 | #include <errno.h> | ||
23 | #include <signal.h> | ||
24 | #include <stdarg.h> | ||
25 | #include <stdio.h> | ||
26 | #include <stdlib.h> | ||
27 | #include <string.h> | ||
28 | #include <unistd.h> | ||
29 | |||
30 | #include "log.h" | ||
31 | #include "ssherr.h" | ||
32 | #include "sshbuf.h" | ||
33 | #include "sshkey.h" | ||
34 | #include "msg.h" | ||
35 | #include "digest.h" | ||
36 | #include "pathnames.h" | ||
37 | #include "ssh-sk.h" | ||
38 | |||
39 | /* #define DEBUG_SK 1 */ | ||
40 | |||
41 | static int | ||
42 | start_helper(int *fdp, pid_t *pidp, void (**osigchldp)(int)) | ||
43 | { | ||
44 | void (*osigchld)(int); | ||
45 | int oerrno, pair[2], r = SSH_ERR_INTERNAL_ERROR; | ||
46 | pid_t pid; | ||
47 | char *helper, *verbosity = NULL; | ||
48 | |||
49 | *fdp = -1; | ||
50 | *pidp = 0; | ||
51 | *osigchldp = SIG_DFL; | ||
52 | |||
53 | helper = getenv("SSH_SK_HELPER"); | ||
54 | if (helper == NULL || strlen(helper) == 0) | ||
55 | helper = _PATH_SSH_SK_HELPER; | ||
56 | #ifdef DEBUG_SK | ||
57 | verbosity = "-vvv"; | ||
58 | #endif | ||
59 | |||
60 | /* Start helper */ | ||
61 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) { | ||
62 | error("socketpair: %s", strerror(errno)); | ||
63 | return SSH_ERR_SYSTEM_ERROR; | ||
64 | } | ||
65 | osigchld = signal(SIGCHLD, SIG_DFL); | ||
66 | if ((pid = fork()) == -1) { | ||
67 | oerrno = errno; | ||
68 | error("fork: %s", strerror(errno)); | ||
69 | close(pair[0]); | ||
70 | close(pair[1]); | ||
71 | signal(SIGCHLD, osigchld); | ||
72 | errno = oerrno; | ||
73 | return SSH_ERR_SYSTEM_ERROR; | ||
74 | } | ||
75 | if (pid == 0) { | ||
76 | if ((dup2(pair[1], STDIN_FILENO) == -1) || | ||
77 | (dup2(pair[1], STDOUT_FILENO) == -1)) { | ||
78 | error("%s: dup2: %s", __func__, ssh_err(r)); | ||
79 | _exit(1); | ||
80 | } | ||
81 | close(pair[0]); | ||
82 | close(pair[1]); | ||
83 | closefrom(STDERR_FILENO + 1); | ||
84 | debug("%s: starting %s %s", __func__, helper, | ||
85 | verbosity == NULL ? "" : verbosity); | ||
86 | execlp(helper, helper, verbosity, (char *)NULL); | ||
87 | error("%s: execlp: %s", __func__, strerror(errno)); | ||
88 | _exit(1); | ||
89 | } | ||
90 | close(pair[1]); | ||
91 | |||
92 | /* success */ | ||
93 | debug3("%s: started pid=%ld", __func__, (long)pid); | ||
94 | *fdp = pair[0]; | ||
95 | *pidp = pid; | ||
96 | *osigchldp = osigchld; | ||
97 | return 0; | ||
98 | } | ||
99 | |||
100 | static int | ||
101 | reap_helper(pid_t pid) | ||
102 | { | ||
103 | int status, oerrno; | ||
104 | |||
105 | debug3("%s: pid=%ld", __func__, (long)pid); | ||
106 | |||
107 | errno = 0; | ||
108 | while (waitpid(pid, &status, 0) == -1) { | ||
109 | if (errno == EINTR) { | ||
110 | errno = 0; | ||
111 | continue; | ||
112 | } | ||
113 | oerrno = errno; | ||
114 | error("%s: waitpid: %s", __func__, strerror(errno)); | ||
115 | errno = oerrno; | ||
116 | return SSH_ERR_SYSTEM_ERROR; | ||
117 | } | ||
118 | if (!WIFEXITED(status)) { | ||
119 | error("%s: helper exited abnormally", __func__); | ||
120 | return SSH_ERR_AGENT_FAILURE; | ||
121 | } else if (WEXITSTATUS(status) != 0) { | ||
122 | error("%s: helper exited with non-zero exit status", __func__); | ||
123 | return SSH_ERR_AGENT_FAILURE; | ||
124 | } | ||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | static int | ||
129 | client_converse(struct sshbuf *req, struct sshbuf **respp) | ||
130 | { | ||
131 | int oerrno, fd, r2, r = SSH_ERR_INTERNAL_ERROR; | ||
132 | pid_t pid; | ||
133 | u_char version; | ||
134 | void (*osigchld)(int); | ||
135 | struct sshbuf *resp = NULL; | ||
136 | |||
137 | *respp = NULL; | ||
138 | |||
139 | if ((r = start_helper(&fd, &pid, &osigchld)) != 0) | ||
140 | return r; | ||
141 | |||
142 | if ((resp = sshbuf_new()) == NULL) { | ||
143 | r = SSH_ERR_ALLOC_FAIL; | ||
144 | goto out; | ||
145 | } | ||
146 | |||
147 | if ((r = ssh_msg_send(fd, SSH_SK_HELPER_VERSION, req)) != 0) { | ||
148 | error("%s: send: %s", __func__, ssh_err(r)); | ||
149 | goto out; | ||
150 | } | ||
151 | if ((r = ssh_msg_recv(fd, resp)) != 0) { | ||
152 | error("%s: receive: %s", __func__, ssh_err(r)); | ||
153 | goto out; | ||
154 | } | ||
155 | if ((r = sshbuf_get_u8(resp, &version)) != 0) { | ||
156 | error("%s: parse version: %s", __func__, ssh_err(r)); | ||
157 | goto out; | ||
158 | } | ||
159 | if (version != SSH_SK_HELPER_VERSION) { | ||
160 | error("%s: unsupported version: got %u, expected %u", | ||
161 | __func__, version, SSH_SK_HELPER_VERSION); | ||
162 | r = SSH_ERR_INVALID_FORMAT; | ||
163 | goto out; | ||
164 | } | ||
165 | /* success */ | ||
166 | r = 0; | ||
167 | out: | ||
168 | oerrno = errno; | ||
169 | close(fd); | ||
170 | if ((r2 = reap_helper(pid)) != 0) { | ||
171 | if (r == 0) { | ||
172 | r = r2; | ||
173 | oerrno = errno; | ||
174 | } | ||
175 | } | ||
176 | if (r == 0) { | ||
177 | *respp = resp; | ||
178 | resp = NULL; | ||
179 | } | ||
180 | sshbuf_free(resp); | ||
181 | signal(SIGCHLD, osigchld); | ||
182 | errno = oerrno; | ||
183 | return r; | ||
184 | |||
185 | } | ||
186 | |||
187 | int | ||
188 | sshsk_sign(const char *provider, struct sshkey *key, | ||
189 | u_char **sigp, size_t *lenp, const u_char *data, size_t datalen, | ||
190 | u_int compat) | ||
191 | { | ||
192 | int oerrno, r = SSH_ERR_INTERNAL_ERROR; | ||
193 | char *fp = NULL; | ||
194 | struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL; | ||
195 | |||
196 | *sigp = NULL; | ||
197 | *lenp = 0; | ||
198 | |||
199 | if ((kbuf = sshbuf_new()) == NULL || | ||
200 | (req = sshbuf_new()) == NULL) { | ||
201 | r = SSH_ERR_ALLOC_FAIL; | ||
202 | goto out; | ||
203 | } | ||
204 | |||
205 | if ((r = sshkey_private_serialize(key, kbuf)) != 0) { | ||
206 | error("%s: serialize private key: %s", __func__, ssh_err(r)); | ||
207 | goto out; | ||
208 | } | ||
209 | if ((r = sshbuf_put_u32(req, SSH_SK_HELPER_SIGN)) != 0 || | ||
210 | (r = sshbuf_put_stringb(req, kbuf)) != 0 || | ||
211 | (r = sshbuf_put_cstring(req, provider)) != 0 || | ||
212 | (r = sshbuf_put_string(req, data, datalen)) != 0 || | ||
213 | (r = sshbuf_put_cstring(req, NULL)) != 0 || /* alg */ | ||
214 | (r = sshbuf_put_u32(req, compat)) != 0) { | ||
215 | error("%s: compose: %s", __func__, ssh_err(r)); | ||
216 | goto out; | ||
217 | } | ||
218 | |||
219 | if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT, | ||
220 | SSH_FP_DEFAULT)) == NULL) { | ||
221 | error("%s: sshkey_fingerprint failed", __func__); | ||
222 | r = SSH_ERR_ALLOC_FAIL; | ||
223 | goto out; | ||
224 | } | ||
225 | if ((r = client_converse(req, &resp)) != 0) | ||
226 | goto out; | ||
227 | |||
228 | if ((r = sshbuf_get_string(resp, sigp, lenp)) != 0) { | ||
229 | error("%s: parse signature: %s", __func__, ssh_err(r)); | ||
230 | r = SSH_ERR_INVALID_FORMAT; | ||
231 | goto out; | ||
232 | } | ||
233 | if (sshbuf_len(resp) != 0) { | ||
234 | error("%s: trailing data in response", __func__); | ||
235 | r = SSH_ERR_INVALID_FORMAT; | ||
236 | goto out; | ||
237 | } | ||
238 | /* success */ | ||
239 | r = 0; | ||
240 | out: | ||
241 | oerrno = errno; | ||
242 | if (r != 0) { | ||
243 | freezero(*sigp, *lenp); | ||
244 | *sigp = NULL; | ||
245 | *lenp = 0; | ||
246 | } | ||
247 | sshbuf_free(kbuf); | ||
248 | sshbuf_free(req); | ||
249 | sshbuf_free(resp); | ||
250 | errno = oerrno; | ||
251 | return r; | ||
252 | } | ||
253 | |||
254 | int | ||
255 | sshsk_enroll(int type, const char *provider_path, const char *application, | ||
256 | uint8_t flags, struct sshbuf *challenge_buf, struct sshkey **keyp, | ||
257 | struct sshbuf *attest) | ||
258 | { | ||
259 | int oerrno, r = SSH_ERR_INTERNAL_ERROR; | ||
260 | struct sshbuf *kbuf = NULL, *abuf = NULL, *req = NULL, *resp = NULL; | ||
261 | struct sshkey *key = NULL; | ||
262 | |||
263 | *keyp = NULL; | ||
264 | if (attest != NULL) | ||
265 | sshbuf_reset(attest); | ||
266 | |||
267 | if (type < 0) | ||
268 | return SSH_ERR_INVALID_ARGUMENT; | ||
269 | |||
270 | if ((abuf = sshbuf_new()) == NULL || | ||
271 | (kbuf = sshbuf_new()) == NULL || | ||
272 | (req = sshbuf_new()) == NULL) { | ||
273 | r = SSH_ERR_ALLOC_FAIL; | ||
274 | goto out; | ||
275 | } | ||
276 | |||
277 | if ((r = sshbuf_put_u32(req, SSH_SK_HELPER_ENROLL)) != 0 || | ||
278 | (r = sshbuf_put_u32(req, (u_int)type)) != 0 || | ||
279 | (r = sshbuf_put_cstring(req, provider_path)) != 0 || | ||
280 | (r = sshbuf_put_cstring(req, application)) != 0 || | ||
281 | (r = sshbuf_put_u8(req, flags)) != 0 || | ||
282 | (r = sshbuf_put_stringb(req, challenge_buf)) != 0) { | ||
283 | error("%s: compose: %s", __func__, ssh_err(r)); | ||
284 | goto out; | ||
285 | } | ||
286 | |||
287 | if ((r = client_converse(req, &resp)) != 0) | ||
288 | goto out; | ||
289 | |||
290 | if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 || | ||
291 | (r = sshbuf_get_stringb(resp, abuf)) != 0) { | ||
292 | error("%s: parse signature: %s", __func__, ssh_err(r)); | ||
293 | r = SSH_ERR_INVALID_FORMAT; | ||
294 | goto out; | ||
295 | } | ||
296 | if (sshbuf_len(resp) != 0) { | ||
297 | error("%s: trailing data in response", __func__); | ||
298 | r = SSH_ERR_INVALID_FORMAT; | ||
299 | goto out; | ||
300 | } | ||
301 | if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) { | ||
302 | error("Unable to parse private key: %s", ssh_err(r)); | ||
303 | goto out; | ||
304 | } | ||
305 | if (attest != NULL && (r = sshbuf_putb(attest, abuf)) != 0) { | ||
306 | error("%s: buffer error: %s", __func__, ssh_err(r)); | ||
307 | goto out; | ||
308 | } | ||
309 | |||
310 | /* success */ | ||
311 | r = 0; | ||
312 | *keyp = key; | ||
313 | key = NULL; | ||
314 | out: | ||
315 | oerrno = errno; | ||
316 | sshkey_free(key); | ||
317 | sshbuf_free(kbuf); | ||
318 | sshbuf_free(abuf); | ||
319 | sshbuf_free(req); | ||
320 | sshbuf_free(resp); | ||
321 | errno = oerrno; | ||
322 | return r; | ||
323 | } | ||