summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.in2
-rw-r--r--sk-api.h63
-rw-r--r--ssh-sk.c377
-rw-r--r--ssh-sk.h49
4 files changed, 490 insertions, 1 deletions
diff --git a/Makefile.in b/Makefile.in
index f22c83673..bac522ad2 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -96,7 +96,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \
96 msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \ 96 msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \
97 ssh-pkcs11.o smult_curve25519_ref.o \ 97 ssh-pkcs11.o smult_curve25519_ref.o \
98 poly1305.o chacha.o cipher-chachapoly.o \ 98 poly1305.o chacha.o cipher-chachapoly.o \
99 ssh-ed25519.o digest-openssl.o digest-libc.o hmac.o \ 99 ssh-ed25519.o ssh-sk.o digest-openssl.o digest-libc.o hmac.o \
100 sc25519.o ge25519.o fe25519.o ed25519.o verify.o hash.o \ 100 sc25519.o ge25519.o fe25519.o ed25519.o verify.o hash.o \
101 kex.o kexdh.o kexgex.o kexecdh.o kexc25519.o \ 101 kex.o kexdh.o kexgex.o kexecdh.o kexc25519.o \
102 kexgexc.o kexgexs.o \ 102 kexgexc.o kexgexs.o \
diff --git a/sk-api.h b/sk-api.h
new file mode 100644
index 000000000..1de733425
--- /dev/null
+++ b/sk-api.h
@@ -0,0 +1,63 @@
1/* $OpenBSD: sk-api.h,v 1.1 2019/10/31 21:16:20 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#ifndef _SK_API_H
19#define _SK_API_H 1
20
21#include <stddef.h>
22#include <stdint.h>
23
24/* Flags */
25#define SSH_SK_USER_PRESENCE_REQD 0x01
26
27struct sk_enroll_response {
28 uint8_t *public_key;
29 size_t public_key_len;
30 uint8_t *key_handle;
31 size_t key_handle_len;
32 uint8_t *signature;
33 size_t signature_len;
34 uint8_t *attestation_cert;
35 size_t attestation_cert_len;
36};
37
38struct sk_sign_response {
39 uint8_t flags;
40 uint32_t counter;
41 uint8_t *sig_r;
42 size_t sig_r_len;
43 uint8_t *sig_s;
44 size_t sig_s_len;
45};
46
47#define SSH_SK_VERSION_MAJOR 0x00010000 /* current API version */
48#define SSH_SK_VERSION_MAJOR_MASK 0xffff0000
49
50/* Return the version of the middleware API */
51uint32_t sk_api_version(void);
52
53/* Enroll a U2F key (private key generation) */
54int sk_enroll(const uint8_t *challenge, size_t challenge_len,
55 const char *application, uint8_t flags,
56 struct sk_enroll_response **enroll_response);
57
58/* Sign a challenge */
59int sk_sign(const uint8_t *message, size_t message_len,
60 const char *application, const uint8_t *key_handle, size_t key_handle_len,
61 uint8_t flags, struct sk_sign_response **sign_response);
62
63#endif /* _SK_API_H */
diff --git a/ssh-sk.c b/ssh-sk.c
new file mode 100644
index 000000000..7d313f57b
--- /dev/null
+++ b/ssh-sk.c
@@ -0,0 +1,377 @@
1/* $OpenBSD: ssh-sk.c,v 1.1 2019/10/31 21:16:20 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/* #define DEBUG_SK 1 */
19
20#include "includes.h"
21
22#include <dlfcn.h>
23#include <stddef.h>
24#include <stdint.h>
25#include <string.h>
26#include <stdio.h>
27
28#include <openssl/objects.h>
29#include <openssl/ec.h>
30
31#include "log.h"
32#include "misc.h"
33#include "sshbuf.h"
34#include "sshkey.h"
35#include "ssherr.h"
36#include "digest.h"
37
38#include "ssh-sk.h"
39#include "sk-api.h"
40
41struct sshsk_provider {
42 char *path;
43 void *dlhandle;
44
45 /* Return the version of the middleware API */
46 uint32_t (*sk_api_version)(void);
47
48 /* Enroll a U2F key (private key generation) */
49 int (*sk_enroll)(const uint8_t *challenge, size_t challenge_len,
50 const char *application, uint8_t flags,
51 struct sk_enroll_response **enroll_response);
52
53 /* Sign a challenge */
54 int (*sk_sign)(const uint8_t *message, size_t message_len,
55 const char *application,
56 const uint8_t *key_handle, size_t key_handle_len,
57 uint8_t flags, struct sk_sign_response **sign_response);
58};
59
60static void
61sshsk_free(struct sshsk_provider *p)
62{
63 if (p == NULL)
64 return;
65 free(p->path);
66 if (p->dlhandle != NULL)
67 dlclose(p->dlhandle);
68 free(p);
69}
70
71static struct sshsk_provider *
72sshsk_open(const char *path)
73{
74 struct sshsk_provider *ret = NULL;
75 uint32_t version;
76
77 if ((ret = calloc(1, sizeof(*ret))) == NULL) {
78 error("%s: calloc failed", __func__);
79 return NULL;
80 }
81 if ((ret->path = strdup(path)) == NULL) {
82 error("%s: strdup failed", __func__);
83 goto fail;
84 }
85 if ((ret->dlhandle = dlopen(path, RTLD_NOW)) == NULL) {
86 error("Security key provider %s dlopen failed: %s",
87 path, dlerror());
88 goto fail;
89 }
90 if ((ret->sk_api_version = dlsym(ret->dlhandle,
91 "sk_api_version")) == NULL) {
92 error("Security key provider %s dlsym(sk_api_version) "
93 "failed: %s", path, dlerror());
94 goto fail;
95 }
96 version = ret->sk_api_version();
97 debug("%s: provider %s implements version 0x%08lx", __func__,
98 ret->path, (u_long)version);
99 if ((version & SSH_SK_VERSION_MAJOR_MASK) != SSH_SK_VERSION_MAJOR) {
100 error("Security key provider %s implements unsupported version "
101 "0x%08lx (supported: 0x%08lx)", path, (u_long)version,
102 (u_long)SSH_SK_VERSION_MAJOR);
103 goto fail;
104 }
105 if ((ret->sk_enroll = dlsym(ret->dlhandle, "sk_enroll")) == NULL) {
106 error("Security key provider %s dlsym(sk_enroll) "
107 "failed: %s", path, dlerror());
108 goto fail;
109 }
110 if ((ret->sk_sign = dlsym(ret->dlhandle, "sk_sign")) == NULL) {
111 error("Security key provider %s dlsym(sk_sign) failed: %s",
112 path, dlerror());
113 goto fail;
114 }
115 /* success */
116 return ret;
117fail:
118 sshsk_free(ret);
119 return NULL;
120}
121
122static void
123sshsk_free_enroll_response(struct sk_enroll_response *r)
124{
125 if (r == NULL)
126 return;
127 freezero(r->key_handle, r->key_handle_len);
128 freezero(r->public_key, r->public_key_len);
129 freezero(r->signature, r->signature_len);
130 freezero(r->attestation_cert, r->attestation_cert_len);
131 freezero(r, sizeof(*r));
132};
133
134static void
135sshsk_free_sign_response(struct sk_sign_response *r)
136{
137 if (r == NULL)
138 return;
139 freezero(r->sig_r, r->sig_r_len);
140 freezero(r->sig_s, r->sig_s_len);
141 freezero(r, sizeof(*r));
142};
143
144int
145sshsk_enroll(const char *provider_path, const char *application,
146 uint8_t flags, struct sshbuf *challenge_buf, struct sshkey **keyp,
147 struct sshbuf *attest)
148{
149 struct sshsk_provider *skp = NULL;
150 struct sshkey *key = NULL;
151 u_char randchall[32];
152 const u_char *challenge;
153 size_t challenge_len;
154 struct sk_enroll_response *resp = NULL;
155 int r = SSH_ERR_INTERNAL_ERROR;
156 struct sshbuf *b = NULL;
157 EC_POINT *q = NULL;
158
159 *keyp = NULL;
160 if (attest)
161 sshbuf_reset(attest);
162 if (provider_path == NULL) {
163 error("%s: missing provider", __func__);
164 r = SSH_ERR_INVALID_ARGUMENT;
165 goto out;
166 }
167 if (application == NULL || *application == '\0') {
168 error("%s: missing application", __func__);
169 r = SSH_ERR_INVALID_ARGUMENT;
170 goto out;
171 }
172 if (challenge_buf == NULL) {
173 debug("%s: using random challenge", __func__);
174 arc4random_buf(randchall, sizeof(randchall));
175 challenge = randchall;
176 challenge_len = sizeof(randchall);
177 } else if (sshbuf_len(challenge_buf) == 0) {
178 error("Missing enrollment challenge");
179 r = SSH_ERR_INVALID_ARGUMENT;
180 goto out;
181 } else {
182 challenge = sshbuf_ptr(challenge_buf);
183 challenge_len = sshbuf_len(challenge_buf);
184 debug3("%s: using explicit challenge len=%zd",
185 __func__, challenge_len);
186 }
187 if ((skp = sshsk_open(provider_path)) == NULL) {
188 r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
189 goto out;
190 }
191 /* XXX validate flags? */
192 /* enroll key */
193 if ((r = skp->sk_enroll(challenge, challenge_len, application,
194 flags, &resp)) != 0) {
195 error("Security key provider %s returned failure %d",
196 provider_path, r);
197 r = SSH_ERR_INVALID_FORMAT; /* XXX error codes in API? */
198 goto out;
199 }
200 /* Check response validity */
201 if (resp->public_key == NULL || resp->key_handle == NULL ||
202 resp->signature == NULL || resp->attestation_cert == NULL) {
203 error("%s: sk_enroll response invalid", __func__);
204 r = SSH_ERR_INVALID_FORMAT;
205 goto out;
206 }
207 /* Assemble key from response */
208 if ((key = sshkey_new(KEY_ECDSA_SK)) == NULL) {
209 error("%s: sshkey_new failed", __func__);
210 r = SSH_ERR_ALLOC_FAIL;
211 goto out;
212 }
213 key->ecdsa_nid = NID_X9_62_prime256v1;
214 key->sk_flags = flags;
215 if ((key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid)) == NULL ||
216 (q = EC_POINT_new(EC_KEY_get0_group(key->ecdsa))) == NULL ||
217 (key->sk_key_handle = sshbuf_new()) == NULL ||
218 (key->sk_reserved = sshbuf_new()) == NULL ||
219 (b = sshbuf_new()) == NULL) {
220 error("%s: allocation failed", __func__);
221 r = SSH_ERR_ALLOC_FAIL;
222 goto out;
223 }
224 if ((r = sshbuf_put_string(b,
225 resp->public_key, resp->public_key_len)) != 0) {
226 error("%s: buffer error: %s", __func__, ssh_err(r));
227 goto out;
228 }
229 if ((key->sk_application = strdup(application)) == NULL) {
230 error("%s: strdup application failed", __func__);
231 r = SSH_ERR_ALLOC_FAIL;
232 goto out;
233 }
234 if ((r = sshbuf_get_ec(b, q, EC_KEY_get0_group(key->ecdsa))) != 0) {
235 error("%s: parse key: %s", __func__, ssh_err(r));
236 r = SSH_ERR_INVALID_FORMAT;
237 goto out;
238 }
239 if (sshkey_ec_validate_public(EC_KEY_get0_group(key->ecdsa), q) != 0) {
240 error("Security key returned invalid ECDSA key");
241 r = SSH_ERR_KEY_INVALID_EC_VALUE;
242 goto out;
243 }
244 if (EC_KEY_set_public_key(key->ecdsa, q) != 1) {
245 /* XXX assume it is a allocation error */
246 error("%s: allocation failed", __func__);
247 r = SSH_ERR_ALLOC_FAIL;
248 goto out;
249 }
250 if ((r = sshbuf_put(key->sk_key_handle, resp->key_handle,
251 resp->key_handle_len)) != 0) {
252 error("%s: buffer error: %s", __func__, ssh_err(r));
253 goto out;
254 }
255 /* Optionally fill in the attestation information */
256 if (attest != NULL) {
257 if ((r = sshbuf_put_cstring(attest, "sk-attest-v00")) != 0 ||
258 (r = sshbuf_put_u32(attest, 1)) != 0 || /* XXX U2F ver */
259 (r = sshbuf_put_string(attest,
260 resp->attestation_cert, resp->attestation_cert_len)) != 0 ||
261 (r = sshbuf_put_string(attest,
262 resp->signature, resp->signature_len)) != 0 ||
263 (r = sshbuf_put_u32(attest, flags)) != 0 || /* XXX right? */
264 (r = sshbuf_put_string(attest, NULL, 0)) != 0) {
265 error("%s: buffer error: %s", __func__, ssh_err(r));
266 goto out;
267 }
268 }
269 /* success */
270 *keyp = key;
271 key = NULL; /* transferred */
272 r = 0;
273 out:
274 EC_POINT_free(q);
275 sshsk_free(skp);
276 sshbuf_free(b);
277 sshkey_free(key);
278 sshsk_free_enroll_response(resp);
279 explicit_bzero(randchall, sizeof(randchall));
280 return r;
281}
282
283int
284sshsk_ecdsa_sign(const char *provider_path, const struct sshkey *key,
285 u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
286 u_int compat)
287{
288 struct sshsk_provider *skp = NULL;
289 int r = SSH_ERR_INTERNAL_ERROR;
290 struct sk_sign_response *resp = NULL;
291 struct sshbuf *inner_sig = NULL, *sig = NULL;
292 uint8_t message[32];
293
294 if (sigp != NULL)
295 *sigp = NULL;
296 if (lenp != NULL)
297 *lenp = 0;
298 if (provider_path == NULL ||
299 sshkey_type_plain(key->type) != KEY_ECDSA_SK ||
300 key->sk_key_handle == NULL ||
301 key->sk_application == NULL || *key->sk_application == '\0') {
302 r = SSH_ERR_INVALID_ARGUMENT;
303 goto out;
304 }
305 if ((skp = sshsk_open(provider_path)) == NULL) {
306 r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
307 goto out;
308 }
309
310 /* hash data to be signed before it goes to the security key */
311 if ((r = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
312 message, sizeof(message))) != 0) {
313 error("%s: hash application failed: %s", __func__, ssh_err(r));
314 r = SSH_ERR_INTERNAL_ERROR;
315 goto out;
316 }
317 if ((r = skp->sk_sign(message, sizeof(message),
318 key->sk_application,
319 sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle),
320 key->sk_flags, &resp)) != 0) {
321 debug("%s: sk_sign failed with code %d", __func__, r);
322 goto out;
323 }
324 if ((sig = sshbuf_new()) == NULL ||
325 (inner_sig = sshbuf_new()) == NULL) {
326 r = SSH_ERR_ALLOC_FAIL;
327 goto out;
328 }
329 /* Prepare inner signature object */
330 if ((r = sshbuf_put_bignum2_bytes(inner_sig,
331 resp->sig_r, resp->sig_r_len)) != 0 ||
332 (r = sshbuf_put_bignum2_bytes(inner_sig,
333 resp->sig_s, resp->sig_s_len)) != 0 ||
334 (r = sshbuf_put_u8(inner_sig, resp->flags)) != 0 ||
335 (r = sshbuf_put_u32(inner_sig, resp->counter)) != 0) {
336 debug("%s: buffer error (inner): %s", __func__, ssh_err(r));
337 goto out;
338 }
339 /* Assemble outer signature */
340 if ((r = sshbuf_put_cstring(sig, sshkey_ssh_name_plain(key))) != 0 ||
341 (r = sshbuf_put_stringb(sig, inner_sig)) != 0) {
342 debug("%s: buffer error (outer): %s", __func__, ssh_err(r));
343 goto out;
344 }
345#ifdef DEBUG_SK
346 fprintf(stderr, "%s: sig_r:\n", __func__);
347 sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
348 fprintf(stderr, "%s: sig_s:\n", __func__);
349 sshbuf_dump_data(resp->sig_s, resp->sig_s_len, stderr);
350 fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
351 __func__, resp->flags, resp->counter);
352 fprintf(stderr, "%s: hashed message:\n", __func__);
353 sshbuf_dump_data(message, sizeof(message), stderr);
354 fprintf(stderr, "%s: inner:\n", __func__);
355 sshbuf_dump(inner_sig, stderr);
356 fprintf(stderr, "%s: sigbuf:\n", __func__);
357 sshbuf_dump(sig, stderr);
358#endif
359 if (sigp != NULL) {
360 if ((*sigp = malloc(sshbuf_len(sig))) == NULL) {
361 r = SSH_ERR_ALLOC_FAIL;
362 goto out;
363 }
364 memcpy(*sigp, sshbuf_ptr(sig), sshbuf_len(sig));
365 }
366 if (lenp != NULL)
367 *lenp = sshbuf_len(sig);
368 /* success */
369 r = 0;
370 out:
371 explicit_bzero(message, sizeof(message));
372 sshsk_free(skp);
373 sshsk_free_sign_response(resp);
374 sshbuf_free(sig);
375 sshbuf_free(inner_sig);
376 return r;
377}
diff --git a/ssh-sk.h b/ssh-sk.h
new file mode 100644
index 000000000..7c1d2b927
--- /dev/null
+++ b/ssh-sk.h
@@ -0,0 +1,49 @@
1/* $OpenBSD: ssh-sk.h,v 1.1 2019/10/31 21:16:20 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#ifndef _SSH_SK_H
19#define _SSH_SK_H 1
20
21struct sshbuf;
22struct sshkey;
23
24/*
25 * Enroll (generate) a new security-key hosted private key via the specified
26 * provider middleware.
27 * If challenge_buf is NULL then a random 256 bit challenge will be used.
28 *
29 * Returns 0 on success or a ssherr.h error code on failure.
30 *
31 * If successful and the attest_data buffer is not NULL then attestation
32 * information is placed there.
33 */
34int sshsk_enroll(const char *provider_path, const char *application,
35 uint8_t flags, struct sshbuf *challenge_buf, struct sshkey **keyp,
36 struct sshbuf *attest);
37
38/*
39 * Calculate an ECDSA_SK signature using the specified key and provider
40 * middleware.
41 *
42 * Returns 0 on success or a ssherr.h error code on failure.
43 */
44int sshsk_ecdsa_sign(const char *provider_path, const struct sshkey *key,
45 u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
46 u_int compat);
47
48#endif /* _SSH_SK_H */
49