blob: c0f6c1cc1f85196f35f8410851591153631548fd [file] [log] [blame]
markus@openbsd.orgcef84a02019-11-12 19:29:54 +00001/* $OpenBSD: ssh-sk.c,v 1.2 2019/11/12 19:29:54 markus Exp $ */
djm@openbsd.orged3467c2019-10-31 21:16:20 +00002/*
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/* #define DEBUG_SK 1 */
19
20#include "includes.h"
21
Damien Miller764d51e2019-11-01 13:34:49 +110022#ifdef ENABLE_SK
23
djm@openbsd.orged3467c2019-10-31 21:16:20 +000024#include <dlfcn.h>
25#include <stddef.h>
26#include <stdint.h>
27#include <string.h>
28#include <stdio.h>
29
30#include <openssl/objects.h>
31#include <openssl/ec.h>
32
33#include "log.h"
34#include "misc.h"
35#include "sshbuf.h"
36#include "sshkey.h"
37#include "ssherr.h"
38#include "digest.h"
39
40#include "ssh-sk.h"
41#include "sk-api.h"
42
43struct sshsk_provider {
44 char *path;
45 void *dlhandle;
46
47 /* Return the version of the middleware API */
48 uint32_t (*sk_api_version)(void);
49
50 /* Enroll a U2F key (private key generation) */
51 int (*sk_enroll)(const uint8_t *challenge, size_t challenge_len,
52 const char *application, uint8_t flags,
53 struct sk_enroll_response **enroll_response);
54
55 /* Sign a challenge */
56 int (*sk_sign)(const uint8_t *message, size_t message_len,
57 const char *application,
58 const uint8_t *key_handle, size_t key_handle_len,
59 uint8_t flags, struct sk_sign_response **sign_response);
60};
61
62static void
63sshsk_free(struct sshsk_provider *p)
64{
65 if (p == NULL)
66 return;
67 free(p->path);
68 if (p->dlhandle != NULL)
69 dlclose(p->dlhandle);
70 free(p);
71}
72
73static struct sshsk_provider *
74sshsk_open(const char *path)
75{
76 struct sshsk_provider *ret = NULL;
77 uint32_t version;
78
79 if ((ret = calloc(1, sizeof(*ret))) == NULL) {
80 error("%s: calloc failed", __func__);
81 return NULL;
82 }
83 if ((ret->path = strdup(path)) == NULL) {
84 error("%s: strdup failed", __func__);
85 goto fail;
86 }
87 if ((ret->dlhandle = dlopen(path, RTLD_NOW)) == NULL) {
88 error("Security key provider %s dlopen failed: %s",
89 path, dlerror());
90 goto fail;
91 }
92 if ((ret->sk_api_version = dlsym(ret->dlhandle,
93 "sk_api_version")) == NULL) {
94 error("Security key provider %s dlsym(sk_api_version) "
95 "failed: %s", path, dlerror());
96 goto fail;
97 }
98 version = ret->sk_api_version();
99 debug("%s: provider %s implements version 0x%08lx", __func__,
100 ret->path, (u_long)version);
101 if ((version & SSH_SK_VERSION_MAJOR_MASK) != SSH_SK_VERSION_MAJOR) {
102 error("Security key provider %s implements unsupported version "
103 "0x%08lx (supported: 0x%08lx)", path, (u_long)version,
104 (u_long)SSH_SK_VERSION_MAJOR);
105 goto fail;
106 }
107 if ((ret->sk_enroll = dlsym(ret->dlhandle, "sk_enroll")) == NULL) {
108 error("Security key provider %s dlsym(sk_enroll) "
109 "failed: %s", path, dlerror());
110 goto fail;
111 }
112 if ((ret->sk_sign = dlsym(ret->dlhandle, "sk_sign")) == NULL) {
113 error("Security key provider %s dlsym(sk_sign) failed: %s",
114 path, dlerror());
115 goto fail;
116 }
117 /* success */
118 return ret;
119fail:
120 sshsk_free(ret);
121 return NULL;
122}
123
124static void
125sshsk_free_enroll_response(struct sk_enroll_response *r)
126{
127 if (r == NULL)
128 return;
129 freezero(r->key_handle, r->key_handle_len);
130 freezero(r->public_key, r->public_key_len);
131 freezero(r->signature, r->signature_len);
132 freezero(r->attestation_cert, r->attestation_cert_len);
133 freezero(r, sizeof(*r));
134};
135
136static void
137sshsk_free_sign_response(struct sk_sign_response *r)
138{
139 if (r == NULL)
140 return;
141 freezero(r->sig_r, r->sig_r_len);
142 freezero(r->sig_s, r->sig_s_len);
143 freezero(r, sizeof(*r));
144};
145
markus@openbsd.orgcef84a02019-11-12 19:29:54 +0000146/* Assemble key from response */
147static int
148sshsk_ecdsa_assemble(struct sk_enroll_response *resp, struct sshkey **keyp)
149{
150 struct sshkey *key = NULL;
151 struct sshbuf *b = NULL;
152 EC_POINT *q = NULL;
153 int r;
154
155 *keyp = NULL;
156 if ((key = sshkey_new(KEY_ECDSA_SK)) == NULL) {
157 error("%s: sshkey_new failed", __func__);
158 r = SSH_ERR_ALLOC_FAIL;
159 goto out;
160 }
161 key->ecdsa_nid = NID_X9_62_prime256v1;
162 if ((key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid)) == NULL ||
163 (q = EC_POINT_new(EC_KEY_get0_group(key->ecdsa))) == NULL ||
164 (b = sshbuf_new()) == NULL) {
165 error("%s: allocation failed", __func__);
166 r = SSH_ERR_ALLOC_FAIL;
167 goto out;
168 }
169 if ((r = sshbuf_put_string(b,
170 resp->public_key, resp->public_key_len)) != 0) {
171 error("%s: buffer error: %s", __func__, ssh_err(r));
172 goto out;
173 }
174 if ((r = sshbuf_get_ec(b, q, EC_KEY_get0_group(key->ecdsa))) != 0) {
175 error("%s: parse key: %s", __func__, ssh_err(r));
176 r = SSH_ERR_INVALID_FORMAT;
177 goto out;
178 }
179 if (sshkey_ec_validate_public(EC_KEY_get0_group(key->ecdsa), q) != 0) {
180 error("Security key returned invalid ECDSA key");
181 r = SSH_ERR_KEY_INVALID_EC_VALUE;
182 goto out;
183 }
184 if (EC_KEY_set_public_key(key->ecdsa, q) != 1) {
185 /* XXX assume it is a allocation error */
186 error("%s: allocation failed", __func__);
187 r = SSH_ERR_ALLOC_FAIL;
188 goto out;
189 }
190 /* success */
191 *keyp = key;
192 key = NULL; /* transferred */
193 r = 0;
194 out:
195 EC_POINT_free(q);
196 sshkey_free(key);
197 sshbuf_free(b);
198 return r;
199}
200
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000201int
202sshsk_enroll(const char *provider_path, const char *application,
203 uint8_t flags, struct sshbuf *challenge_buf, struct sshkey **keyp,
204 struct sshbuf *attest)
205{
206 struct sshsk_provider *skp = NULL;
207 struct sshkey *key = NULL;
208 u_char randchall[32];
209 const u_char *challenge;
210 size_t challenge_len;
211 struct sk_enroll_response *resp = NULL;
212 int r = SSH_ERR_INTERNAL_ERROR;
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000213
214 *keyp = NULL;
215 if (attest)
216 sshbuf_reset(attest);
217 if (provider_path == NULL) {
218 error("%s: missing provider", __func__);
219 r = SSH_ERR_INVALID_ARGUMENT;
220 goto out;
221 }
222 if (application == NULL || *application == '\0') {
223 error("%s: missing application", __func__);
224 r = SSH_ERR_INVALID_ARGUMENT;
225 goto out;
226 }
227 if (challenge_buf == NULL) {
228 debug("%s: using random challenge", __func__);
229 arc4random_buf(randchall, sizeof(randchall));
230 challenge = randchall;
231 challenge_len = sizeof(randchall);
232 } else if (sshbuf_len(challenge_buf) == 0) {
233 error("Missing enrollment challenge");
234 r = SSH_ERR_INVALID_ARGUMENT;
235 goto out;
236 } else {
237 challenge = sshbuf_ptr(challenge_buf);
238 challenge_len = sshbuf_len(challenge_buf);
239 debug3("%s: using explicit challenge len=%zd",
240 __func__, challenge_len);
241 }
242 if ((skp = sshsk_open(provider_path)) == NULL) {
243 r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
244 goto out;
245 }
246 /* XXX validate flags? */
247 /* enroll key */
248 if ((r = skp->sk_enroll(challenge, challenge_len, application,
249 flags, &resp)) != 0) {
250 error("Security key provider %s returned failure %d",
251 provider_path, r);
252 r = SSH_ERR_INVALID_FORMAT; /* XXX error codes in API? */
253 goto out;
254 }
255 /* Check response validity */
256 if (resp->public_key == NULL || resp->key_handle == NULL ||
257 resp->signature == NULL || resp->attestation_cert == NULL) {
258 error("%s: sk_enroll response invalid", __func__);
259 r = SSH_ERR_INVALID_FORMAT;
260 goto out;
261 }
markus@openbsd.orgcef84a02019-11-12 19:29:54 +0000262 if ((r = sshsk_ecdsa_assemble(resp, &key)) != 0)
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000263 goto out;
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000264 key->sk_flags = flags;
markus@openbsd.orgcef84a02019-11-12 19:29:54 +0000265 if ((key->sk_key_handle = sshbuf_new()) == NULL ||
266 (key->sk_reserved = sshbuf_new()) == NULL) {
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000267 error("%s: allocation failed", __func__);
268 r = SSH_ERR_ALLOC_FAIL;
269 goto out;
270 }
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000271 if ((key->sk_application = strdup(application)) == NULL) {
272 error("%s: strdup application failed", __func__);
273 r = SSH_ERR_ALLOC_FAIL;
274 goto out;
275 }
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000276 if ((r = sshbuf_put(key->sk_key_handle, resp->key_handle,
277 resp->key_handle_len)) != 0) {
278 error("%s: buffer error: %s", __func__, ssh_err(r));
279 goto out;
280 }
281 /* Optionally fill in the attestation information */
282 if (attest != NULL) {
283 if ((r = sshbuf_put_cstring(attest, "sk-attest-v00")) != 0 ||
284 (r = sshbuf_put_u32(attest, 1)) != 0 || /* XXX U2F ver */
285 (r = sshbuf_put_string(attest,
286 resp->attestation_cert, resp->attestation_cert_len)) != 0 ||
287 (r = sshbuf_put_string(attest,
288 resp->signature, resp->signature_len)) != 0 ||
289 (r = sshbuf_put_u32(attest, flags)) != 0 || /* XXX right? */
290 (r = sshbuf_put_string(attest, NULL, 0)) != 0) {
291 error("%s: buffer error: %s", __func__, ssh_err(r));
292 goto out;
293 }
294 }
295 /* success */
296 *keyp = key;
297 key = NULL; /* transferred */
298 r = 0;
299 out:
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000300 sshsk_free(skp);
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000301 sshkey_free(key);
302 sshsk_free_enroll_response(resp);
303 explicit_bzero(randchall, sizeof(randchall));
304 return r;
305}
306
307int
308sshsk_ecdsa_sign(const char *provider_path, const struct sshkey *key,
309 u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
310 u_int compat)
311{
312 struct sshsk_provider *skp = NULL;
313 int r = SSH_ERR_INTERNAL_ERROR;
314 struct sk_sign_response *resp = NULL;
315 struct sshbuf *inner_sig = NULL, *sig = NULL;
316 uint8_t message[32];
317
318 if (sigp != NULL)
319 *sigp = NULL;
320 if (lenp != NULL)
321 *lenp = 0;
322 if (provider_path == NULL ||
323 sshkey_type_plain(key->type) != KEY_ECDSA_SK ||
324 key->sk_key_handle == NULL ||
325 key->sk_application == NULL || *key->sk_application == '\0') {
326 r = SSH_ERR_INVALID_ARGUMENT;
327 goto out;
328 }
329 if ((skp = sshsk_open(provider_path)) == NULL) {
330 r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
331 goto out;
332 }
333
334 /* hash data to be signed before it goes to the security key */
335 if ((r = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
336 message, sizeof(message))) != 0) {
337 error("%s: hash application failed: %s", __func__, ssh_err(r));
338 r = SSH_ERR_INTERNAL_ERROR;
339 goto out;
340 }
341 if ((r = skp->sk_sign(message, sizeof(message),
342 key->sk_application,
343 sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle),
344 key->sk_flags, &resp)) != 0) {
345 debug("%s: sk_sign failed with code %d", __func__, r);
346 goto out;
347 }
348 if ((sig = sshbuf_new()) == NULL ||
349 (inner_sig = sshbuf_new()) == NULL) {
350 r = SSH_ERR_ALLOC_FAIL;
351 goto out;
352 }
353 /* Prepare inner signature object */
354 if ((r = sshbuf_put_bignum2_bytes(inner_sig,
355 resp->sig_r, resp->sig_r_len)) != 0 ||
356 (r = sshbuf_put_bignum2_bytes(inner_sig,
357 resp->sig_s, resp->sig_s_len)) != 0 ||
358 (r = sshbuf_put_u8(inner_sig, resp->flags)) != 0 ||
359 (r = sshbuf_put_u32(inner_sig, resp->counter)) != 0) {
360 debug("%s: buffer error (inner): %s", __func__, ssh_err(r));
361 goto out;
362 }
363 /* Assemble outer signature */
364 if ((r = sshbuf_put_cstring(sig, sshkey_ssh_name_plain(key))) != 0 ||
365 (r = sshbuf_put_stringb(sig, inner_sig)) != 0) {
366 debug("%s: buffer error (outer): %s", __func__, ssh_err(r));
367 goto out;
368 }
369#ifdef DEBUG_SK
370 fprintf(stderr, "%s: sig_r:\n", __func__);
371 sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
372 fprintf(stderr, "%s: sig_s:\n", __func__);
373 sshbuf_dump_data(resp->sig_s, resp->sig_s_len, stderr);
374 fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
375 __func__, resp->flags, resp->counter);
376 fprintf(stderr, "%s: hashed message:\n", __func__);
377 sshbuf_dump_data(message, sizeof(message), stderr);
378 fprintf(stderr, "%s: inner:\n", __func__);
379 sshbuf_dump(inner_sig, stderr);
380 fprintf(stderr, "%s: sigbuf:\n", __func__);
381 sshbuf_dump(sig, stderr);
382#endif
383 if (sigp != NULL) {
384 if ((*sigp = malloc(sshbuf_len(sig))) == NULL) {
385 r = SSH_ERR_ALLOC_FAIL;
386 goto out;
387 }
388 memcpy(*sigp, sshbuf_ptr(sig), sshbuf_len(sig));
389 }
390 if (lenp != NULL)
391 *lenp = sshbuf_len(sig);
392 /* success */
393 r = 0;
394 out:
395 explicit_bzero(message, sizeof(message));
396 sshsk_free(skp);
397 sshsk_free_sign_response(resp);
398 sshbuf_free(sig);
399 sshbuf_free(inner_sig);
400 return r;
401}
Damien Miller764d51e2019-11-01 13:34:49 +1100402#endif /* ENABLE_SK */