summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ssh-sk-client.c323
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
41static int
42start_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
100static int
101reap_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
128static int
129client_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
187int
188sshsk_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
254int
255sshsk_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}