~pedro/fido2-webauthn-client

a1f294f962a2575ad54c53bc30e16493dc473d6b — pedro martelletto 5 months ago
initial import
A  => CMakeLists.txt +73 -0
@@ 1,73 @@
# Copyright (c) 2020 Pedro Martelletto. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

project(webauthn_client C)
cmake_minimum_required(VERSION 3.0)
include(FindPkgConfig)

set(CMAKE_COLOR_MAKEFILE OFF)
set(CMAKE_VERBOSE_MAKEFILE ON)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wshadow")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wwrite-strings")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wmissing-prototypes")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wbad-function-cast")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pedantic")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pedantic-errors")

pkg_search_module(LIBCURL libcurl REQUIRED)
pkg_search_module(JANSSON jansson REQUIRED)
pkg_search_module(LIBCBOR libcbor REQUIRED)
pkg_search_module(LIBCRYPTO libcrypto REQUIRED)
pkg_search_module(LIBFIDO2 libfido2 REQUIRED)

message(STATUS "LIBCURL_INCLUDE_DIRS: ${LIBCURL_INCLUDE_DIRS}")
message(STATUS "LIBCURL_LIBRARY_DIRS: ${LIBCURL_LIBRARY_DIRS}")
message(STATUS "LIBCURL_LIBRARIES: ${LIBCURL_LIBRARIES}")
message(STATUS "JANSSON_INCLUDE_DIRS: ${JANSSON_INCLUDE_DIRS}")
message(STATUS "JANSSON_LIBRARY_DIRS: ${JANSSON_LIBRARY_DIRS}")
message(STATUS "JANSSON_LIBRARIES: ${JANSSON_LIBRARIES}")
message(STATUS "LIBCBOR_INCLUDE_DIRS: ${LIBCBOR_INCLUDE_DIRS}")
message(STATUS "LIBCBOR_LIBRARY_DIRS: ${LIBCBOR_LIBRARY_DIRS}")
message(STATUS "LIBCBOR_LIBRARIES: ${LIBCBOR_LIBRARIES}")
message(STATUS "LIBCRYPTO_INCLUDE_DIRS: ${LIBCRYPTO_INCLUDE_DIRS}")
message(STATUS "LIBCRYPTO_LIBRARY_DIRS: ${LIBCRYPTO_LIBRARY_DIRS}")
message(STATUS "LIBCRYPTO_LIBRARIES: ${LIBCRYPTO_LIBRARIES}")
message(STATUS "LIBFIDO2_INCLUDE_DIRS: ${LIBFIDO2_INCLUDE_DIRS}")
message(STATUS "LIBFIDO2_LIBRARY_DIRS: ${LIBFIDO2_LIBRARY_DIRS}")
message(STATUS "LIBFIDO2_LIBRARIES: ${LIBFIDO2_LIBRARIES}")

include_directories(${LIBCURL_INCLUDE_DIRS})
include_directories(${JANSSON_INCLUDE_DIRS})
include_directories(${LIBCRYPTO_INCLUDE_DIRS})
include_directories(${LIBCBOR_INCLUDE_DIRS})
include_directories(${LIBFIDO2_INCLUDE_DIRS})

link_directories(${LIBCURL_LIBRARY_DIRS})
link_directories(${JANSSON_LIBRARY_DIRS})
link_directories(${LIBCBOR_LIBRARY_DIRS})
link_directories(${LIBCRYPTO_LIBRARY_DIRS})
link_directories(${LIBFIDO2_LIBRARY_DIRS})

add_executable(webauthn-client
	base64.c
	cbor.c
	clientdata.c
	http.c
	json.c
	webauthn-assert.c
	webauthn-client.c
	webauthn-cred.c
)

target_link_libraries(webauthn-client
	${JANSSON_LIBRARIES}
	${LIBCBOR_LIBRARIES}
	${LIBCRYPTO_LIBRARIES}
	${LIBCURL_LIBRARIES}
	${LIBFIDO2_LIBRARIES}
)

A  => LICENSE +24 -0
@@ 1,24 @@
Copyright (c) 2020 Pedro Martelletto. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

   1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
   2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in
      the documentation and/or other materials provided with the
      distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

A  => README +4 -0
@@ 1,4 @@
libfido2-webauthn-client

An implementation of a dummy WebAuthn client using libfido2, for
illustration purposes only, and provided as-is.

A  => base64.c +104 -0
@@ 1,104 @@
/*
 * Copyright (c) 2018 Yubico AB. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#include <openssl/bio.h>
#include <openssl/evp.h>

#include <limits.h>
#include <string.h>

#include "base64.h"

int
base64_decode(const char *in, void **ptr, size_t *len)
{
	BIO    *bio_mem = NULL;
	BIO    *bio_b64 = NULL;
	size_t  alloc_len;
	int     n;
	int     ok = -1;

	if (in == NULL || ptr == NULL || len == NULL || strlen(in) > INT_MAX)
		return -1;

	*ptr = NULL;
	*len = 0;

	if ((bio_b64 = BIO_new(BIO_f_base64())) == NULL)
		goto fail;
	if ((bio_mem = BIO_new_mem_buf((const void *)in, -1)) == NULL)
		goto fail;

	BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL);
	BIO_push(bio_b64, bio_mem);

	alloc_len = strlen(in);
	if ((*ptr = calloc(1, alloc_len)) == NULL)
		goto fail;

	n = BIO_read(bio_b64, *ptr, (int)alloc_len);
	if (n <= 0 || BIO_eof(bio_b64) == 0)
		goto fail;

	*len = (size_t)n;
	ok = 0;
fail:
	BIO_free(bio_b64);
	BIO_free(bio_mem);

	if (ok < 0) {
		free(*ptr);
		*ptr = NULL;
		*len = 0;
	}

	return ok;
}

int
base64_encode(const void *ptr, size_t len, char **out)
{
	BIO  *bio_b64 = NULL;
	BIO  *bio_mem = NULL;
	char *b64_ptr = NULL;
	long  b64_len;
	int   n;
	int   ok = -1;

	if (ptr == NULL || out == NULL || len > INT_MAX)
		return -1;

	*out = NULL;

	if ((bio_b64 = BIO_new(BIO_f_base64())) == NULL)
		goto fail;
	if ((bio_mem = BIO_new(BIO_s_mem())) == NULL)
		goto fail;

	BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL);
	BIO_push(bio_b64, bio_mem);

	n = BIO_write(bio_b64, ptr, (int)len);
	if (n < 0 || (size_t)n != len)
		goto fail;

	if (BIO_flush(bio_b64) < 0)
		goto fail;

	b64_len = BIO_get_mem_data(bio_b64, &b64_ptr);
	if (b64_len < 0 || (size_t)b64_len == SIZE_MAX || b64_ptr == NULL)
		goto fail;
	if ((*out = calloc(1, (size_t)b64_len + 1)) == NULL)
		goto fail;

	memcpy(*out, b64_ptr, (size_t)b64_len);
	ok = 0;
fail:
	BIO_free(bio_b64);
	BIO_free(bio_mem);

	return ok;
}

A  => base64.h +13 -0
@@ 1,13 @@
/*
 * Copyright (c) 2018 Yubico AB. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#ifndef _BASE64_H_
#define _BASE64_H_

int base64_decode(const char *, void **, size_t *);
int base64_encode(const void *, size_t, char **);

#endif

A  => cbor.c +239 -0
@@ 1,239 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#include <cbor.h>
#include <err.h>
#include <fido.h>
#include <string.h>

#include "base64.h"
#include "cbor.h"

static void
free_pair(struct cbor_pair **pp)
{
	struct cbor_pair *p;

	if (pp == NULL || (p = *pp) == NULL)
		return;
	if (p->key)
		cbor_decref(&p->key);
	if (p->value)
		cbor_decref(&p->value);

	free(p);
	*pp = NULL;
}

static struct cbor_pair *
cbor_pack_item(const char *key, cbor_item_t **item)
{
	struct cbor_pair *p;

	if ((p = calloc(1, sizeof(*p))) == NULL ||
	    (p->key = cbor_build_string(key)) == NULL ||
	    *item == NULL) {
		warnx("%s: %s", __func__, key);
		free_pair(&p);
		return NULL;
	}

	p->value = *item;
	*item = NULL;

	return p;
}

static struct cbor_pair *
cbor_pack_str(const char *key, const char *val)
{
	cbor_item_t *item = cbor_build_string(val);

	return cbor_pack_item(key, &item);
}

static struct cbor_pair *
cbor_pack_cose(const char *key, int type)
{
	cbor_item_t *item = cbor_build_negint8(-type - 1);

	return cbor_pack_item(key, &item);
}

static struct cbor_pair *
cbor_pack_blob(const char *key, const uint8_t *ptr, size_t len)
{
	cbor_item_t *item = cbor_build_bytestring(ptr, len);

	return cbor_pack_item(key, &item);
}

static struct cbor_pair *
cbor_wrap_blob(const char *key, const uint8_t *ptr, size_t len)
{
	cbor_item_t		*array = NULL;
	cbor_item_t		*blob = NULL;
	struct cbor_pair	*p = NULL;

	if ((blob = cbor_build_bytestring(ptr, len)) == NULL ||
	    (array = cbor_new_definite_array(1)) == NULL ||
	    cbor_array_push(array, blob) == false ||
	    (p = cbor_pack_item(key, &array)) == NULL) {
		warnx("%s: %s", __func__, key);
	}

	if (blob)
		cbor_decref(&blob);
	if (array)
		cbor_decref(&array);

	return p;
}

static cbor_item_t *
cbor_encode_attestation_statement(const fido_cred_t *c, const char *fmt)
{
	cbor_item_t		*attstmt = NULL;
	const unsigned char	*sig_ptr;
	const unsigned char	*x5c_ptr;
	int			 ok = -1;
	int			 type;
	size_t			 sig_len;
	size_t			 x5c_len;
	struct cbor_pair	*argv[3];

	memset(argv, 0, sizeof(argv));

	if ((type = fido_cred_type(c)) != COSE_ES256 ||
	    (sig_ptr = fido_cred_sig_ptr(c)) == NULL ||
	    (sig_len = fido_cred_sig_len(c)) == 0 ||
	    (x5c_ptr = fido_cred_x5c_ptr(c)) == NULL ||
	    (x5c_len = fido_cred_x5c_len(c)) == 0) {
		warnx("%s: fido_cred", __func__);
		goto fail;
	}

	if ((attstmt = cbor_new_definite_map(3)) == NULL) {
		warnx("%s: cbor_new_definite_map", __func__);
		goto fail;
	}

	if ((argv[0] = cbor_pack_cose("alg", type)) == NULL ||
	    (argv[1] = cbor_pack_blob("sig", sig_ptr, sig_len)) == NULL ||
	    (argv[2] = cbor_wrap_blob("x5c", x5c_ptr, x5c_len)) == NULL) {
		warnx("%s: cbor_pack", __func__);
		goto fail;
	}

	if (cbor_map_add(attstmt, *argv[1]) == false ||
	    cbor_map_add(attstmt, *argv[2]) == false) {
		warnx("%s: cbor_map_add", __func__);
		goto fail;
	}

	if (strcmp(fmt, "packed") == 0) {
		if (cbor_map_add(attstmt, *argv[0]) == false) {
			warnx("%s: cbor_map_add", __func__);
			goto fail;
		}
	}

	ok = 0;
fail:
	for (size_t i = 0; i < 3; i++)
		free_pair(&argv[i]);

	if (ok < 0 && attstmt != NULL)
		cbor_decref(&attstmt);

	return attstmt;
}

static int
base64_encode_cbor(const cbor_item_t *item, char **out)
{
	unsigned char	*ptr = NULL;
	size_t		 len;
	size_t		 alloc_len;

	if ((len = cbor_serialize_alloc(item, &ptr, &alloc_len)) == 0 ||
	    base64_encode(ptr, len, out) < 0) {
		free(ptr);
		return -1;
	}

	free(ptr);

	return 0;
}

char *
cbor_build_attestation_object(const fido_cred_t *c)
{
	cbor_item_t		*attobj = NULL;
	cbor_item_t		*attstmt = NULL;
	cbor_item_t		*authdata = NULL;
	char			*attobj_b64 = NULL;
	const char		*fmt;
	const unsigned char	*authdata_ptr;
	size_t			 authdata_len;
	struct cbor_load_result	 cbor;
	struct cbor_pair	*argv[3];

	memset(argv, 0, sizeof(argv));

	if ((fmt = fido_cred_fmt(c)) == NULL ||
	    (attstmt = cbor_encode_attestation_statement(c, fmt)) == NULL ||
	    (authdata_ptr = fido_cred_authdata_ptr(c)) == NULL ||
	    (authdata_len = fido_cred_authdata_len(c)) == 0) {
		warnx("%s: fido_cred", __func__);
		goto fail;
	}

	if ((authdata = cbor_load(authdata_ptr, authdata_len, &cbor)) == NULL) {
		warnx("%s: cbor_load", __func__);
		goto fail;
	}

	if ((attobj = cbor_new_definite_map(3)) == NULL) {
		warnx("%s: cbor_new_definite_map", __func__);
		goto fail;
	}

	if ((argv[0] = cbor_pack_str("fmt", fmt)) == NULL ||
	    (argv[1] = cbor_pack_item("attStmt", &attstmt)) == NULL ||
	    (argv[2] = cbor_pack_item("authData", &authdata)) == NULL) {
		warnx("%s: cbor_pack", __func__);
		goto fail;
	}

	if (cbor_map_add(attobj, *argv[0]) == false ||
	    cbor_map_add(attobj, *argv[1]) == false ||
	    cbor_map_add(attobj, *argv[2]) == false) {
		warnx("%s: cbor_map_add", __func__);
		goto fail;
	}

	if (base64_encode_cbor(attobj, &attobj_b64) < 0) {
		warnx("%s: base64_encode_cbor", __func__);
		goto fail;
	}

fail:
	if (attobj != NULL)
		cbor_decref(&attobj);

	if (attstmt != NULL)
		cbor_decref(&attstmt);

	if (authdata != NULL)
		cbor_decref(&authdata);

	for (size_t i = 0; i < 3; i++)
		free_pair(&argv[i]);

	return attobj_b64;
}

A  => cbor.h +12 -0
@@ 1,12 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#ifndef _CBOR_H_
#define _CBOR_H_

char *cbor_build_attestation_object(const fido_cred_t *);

#endif

A  => clientdata.c +47 -0
@@ 1,47 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "base64.h"
#include "clientdata.h"
#include "param.h"

char *
clientdata_json(const char *type, const char *challenge)
{
	char	 buf[MAX_CLIENTDATA_JSON_LEN];
	int	 n;
	int	 ok = -1;
	void	*ptr = NULL;
	size_t	 len = 0;

	memset(buf, 0, sizeof(buf));

	if (base64_decode(challenge, &ptr, &len) < 0 || len < 32) {
		warnx("%s: challenge", __func__);
		goto fail;
	}

	if ((n = snprintf(buf, sizeof(buf), "{\"type\":\"%s\",\"challenge\":"
	    "\"%s\",\"origin\":\"%s\",\"crossOrigin\":false}", type, challenge,
	    ORIGIN)) < 0 || (size_t)n >= sizeof(buf)) {
		warnx("%s: snprintf", __func__);
		goto fail;
	}

	ok = 0;
fail:
	free(ptr);

	if (ok < 0)
		return NULL;

	return strdup(buf);
}

A  => clientdata.h +12 -0
@@ 1,12 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#ifndef _CLIENTDATA_H_
#define _CLIENTDATA_H_

char *clientdata_json(const char *, const char *);

#endif

A  => http.c +279 -0
@@ 1,279 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <curl/curl.h>

#include "http.h"
#include "param.h"

#define HTTP_CONTENT_TYPE	"Content-Type: application/json"

struct http_post {
	char			*url;
	char			*request;
	char			*request_cookie;
	char			*response;
	char			*response_cookie;
	CURL			*curl;
	FILE			*fp;
	struct curl_slist	*header;
};

static size_t
sink(void *body, size_t size, size_t nmemb, void *p)
{
	return fwrite(body, size, nmemb, (FILE *)p);
}

int
http_init(void)
{
	if (curl_global_init(CURL_GLOBAL_DEFAULT) != 0) {
		warnx("%s: curl_global_init", __func__);
		return -1;
	}

	return 0;
}

void
http_exit(void)
{
	curl_global_cleanup();
}

struct http_post *
http_new(const char *url, const char *request)
{
	struct http_post	*h;
	int			 ok = -1;

	if ((h = calloc(1, sizeof(*h))) == NULL) {
		warnx("%s: calloc http", __func__);
		return NULL;
	}

	if ((h->url = strdup(url)) == NULL) {
		warnx("%s: strdup url", __func__);
		goto fail;
	}

	if ((h->request = strdup(request)) == NULL) {
		warnx("%s: strdup request", __func__);
		goto fail;
	}

	if ((h->response = calloc(1, MAX_RESPONSE_LEN)) == NULL) {
		warnx("%s: calloc response", __func__);
		goto fail;
	}

	if ((h->curl = curl_easy_init()) == NULL) {
		warnx("%s: curl_easy_init", __func__);
		goto fail;
	}

	if ((h->header = curl_slist_append(NULL, HTTP_CONTENT_TYPE)) == NULL) {
		warnx("%s: curl_slist_append", __func__);
		goto fail;
	}

	if (curl_easy_setopt(h->curl, CURLOPT_HTTPHEADER, h->header) != 0 ||
	    curl_easy_setopt(h->curl, CURLOPT_URL, h->url) != 0 ||
	    curl_easy_setopt(h->curl, CURLOPT_POSTFIELDS, h->request) != 0 ||
	    curl_easy_setopt(h->curl, CURLOPT_COOKIEFILE, "") != 0) {
		warnx("%s: curl_easy_setopt", __func__);
		goto fail;
	}

	ok = 0;
fail:
	if (ok < 0)
		http_free(&h);

	return h;
}

void
http_free(struct http_post **hp)
{
	struct http_post *h;

	if (hp == NULL || (h = *hp) == NULL)
		return;

	free(h->url);
	free(h->request);
	free(h->request_cookie);
	free(h->response);
	free(h->response_cookie);

	if (h->curl)
		curl_easy_cleanup(h->curl);
	if (h->header)
		curl_slist_free_all(h->header);

	free(h);
	*hp = NULL;
}

int
http_set_cookie(struct http_post *h, const char *cookie)
{
	if (h->request_cookie) {
		warnx("%s: cookie set", __func__);
		return -1;
	}

	if ((h->request_cookie = strdup(cookie)) == NULL) {
		warnx("%s: strdup", __func__);
		return -1;
	}

	if (curl_easy_setopt(h->curl, CURLOPT_COOKIELIST,
	    h->request_cookie) != 0) {
		warnx("%s: curl_easy_setopt", __func__);
		free(h->request_cookie);
		h->request_cookie = NULL;
		return -1;
	}

	return 0;
}

int
http_post(struct http_post *h)
{
	CURLcode		 r;
	int			 ok = -1;
	struct curl_slist	*cookie = NULL;

	if ((h->fp = fmemopen(h->response, MAX_RESPONSE_LEN, "w")) == NULL) {
		warnx("%s: fmemopen", __func__);
		return -1;
	}

	if (curl_easy_setopt(h->curl, CURLOPT_WRITEFUNCTION, sink) != 0 ||
	    curl_easy_setopt(h->curl, CURLOPT_WRITEDATA, h->fp) != 0) {
		warnx("%s: curl_easy_setopt", __func__);
		goto fail;
	}

	if ((r = curl_easy_perform(h->curl)) != CURLE_OK) {
		warnx("%s: curl_easy_perform: %s", __func__,
		    curl_easy_strerror(r));
		goto fail;
	}

	if (curl_easy_getinfo(h->curl, CURLINFO_COOKIELIST, &cookie) != 0) {
		warnx("%s: curl_easy_getinfo", __func__);
		goto fail;
	}

	if (cookie == NULL || cookie->data == NULL) {
		warnx("%s: response cookie", __func__);
		goto fail;
	}

	if ((h->response_cookie = strdup(cookie->data)) == NULL) {
		warnx("%s: strdup", __func__);
		goto fail;
	}

	ok = 0;
fail:
	fclose(h->fp);
	h->fp = NULL;

	if (cookie)
		curl_slist_free_all(cookie);

	return ok;
}

int
http_request(struct http_post **h, const char *url, const char *body,
    const char *cookie)
{
	json_t		*blob;
	json_error_t	 error;

	warnx("%s: %s", __func__, url);

	if ((blob = json_loads(body, 0, &error)) == NULL) {
		warnx("%s: json_loads", __func__);
		return -1;
	}

	if (json_dumpf(blob, stderr, JSON_INDENT(2)) < 0)
		warnx("%s: json_dumpf", __func__);
	else
		fputc('\n', stderr);

	json_decref(blob);
	blob = NULL;

	if ((*h = http_new(url, body)) == NULL) {
		warnx("%s: http_new", __func__);
		return -1;
	}

	if (cookie && http_set_cookie(*h, cookie) < 0) {
		warnx("%s: http_set_cookie", __func__);
		return -1;
	}

	if (http_post(*h) < 0) {
		warnx("%s: http_post", __func__);
		return -1;
	}

	return 0;
}

const char *
http_response(const struct http_post *h)
{
	return h->response;
}

const char *
http_response_cookie(const struct http_post *h)
{
	return h->response_cookie;
}

json_t *
http_response_json(struct http_post *h)
{
	json_error_t	 error;
	json_t		*blob;

	warnx("%s: %s", __func__, h->url);

	if ((blob = json_loads(h->response, 0, &error)) == NULL) {
		warnx("%s: json_loads", __func__);
		return NULL;
	}

	if (json_is_object(blob) == 0) {
		warnx("%s: json_is_object", __func__);
		json_decref(blob);
		return NULL;
	}

	if (json_dumpf(blob, stderr, JSON_INDENT(2)) < 0)
		warnx("%s: json_dumpf", __func__);
	else
		fputc('\n', stderr);

	return blob;
}

A  => http.h +25 -0
@@ 1,25 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#ifndef _HTTP_H_
#define _HTTP_H_

#include <jansson.h>

struct http_post;

int			 http_init(void);
int			 http_set_cookie(struct http_post *, const char *);
int			 http_post(struct http_post *);
int			 http_request(struct http_post **, const char *, const char *, const char *);
void			 http_exit(void);
void			 http_free(struct http_post **);
struct http_post	*http_new(const char *, const char *);
const char		*http_response(const struct http_post *);
const char		*http_response_cookie(const struct http_post *);
json_t			*http_response_json(struct http_post *);

#endif

A  => json.c +98 -0
@@ 1,98 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#include <err.h>
#include <jansson.h>
#include <stdlib.h>
#include <string.h>

#include "base64.h"
#include "json.h"

json_t *
json_parse_obj(const json_t *obj, const char *key)
{
	json_t *val;

	if ((val = json_object_get(obj, key)) == NULL ||
	    json_is_object(val) == 0) {
		warnx("%s: json %s", __func__, key);
		return NULL;
	}

	return val;
}

char *
json_parse_str(const json_t *obj, const char *key)
{
	json_t *val;

	if ((val = json_object_get(obj, key)) == NULL ||
	    json_is_string(val) == 0) {
		warnx("%s: json %s", __func__, key);
		return NULL;
	}

	return strdup(json_string_value(val));
}

int
json_check_str(const json_t *obj, const char *key, const char *expected_val)
{
	json_t *val;

	if ((val = json_object_get(obj, key)) == NULL ||
	    json_is_string(val) == 0 ||
	    strcmp(json_string_value(val), expected_val) != 0) {
		warnx("%s: json %s != %s", __func__, key, expected_val);
		return -1;
	}

	return 0;
}

int
json_parse_blob(const json_t *obj, const char *key, void **ptr, size_t *len)
{
	json_t *blob;

	if ((blob = json_object_get(obj, key)) == NULL ||
	    json_is_string(blob) == 0 ||
	    base64_decode(json_string_value(blob), ptr, len) < 0) {
		warnx("%s json %s", __func__, key);
		return -1;
	}

	return 0;
}
 
int
json_parse_allowcred(const json_t *obj, void **ptr, size_t *len)
{
	json_t	*allowcred;
	json_t	*cred;

	if ((allowcred = json_object_get(obj, "allowCredentials")) == NULL ||
	    json_is_array(allowcred) == 0 || json_array_size(allowcred) != 1) {
		warnx("%s: json", __func__);
		return -1;
	}

	if ((cred = json_array_get(allowcred, 0)) == NULL ||
	    json_is_object(cred) == 0) {
		warnx("%s: json cred", __func__);
		return -1;
	}

	if (json_check_str(cred, "type", "public-key") < 0 ||
	    json_parse_blob(cred, "id", ptr, len) < 0) {
		warnx("%s: json", __func__);
		return -1;
	}

	return 0;
}

A  => json.h +16 -0
@@ 1,16 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#ifndef _JSON_H_
#define _JSON_H_

json_t *	json_parse_obj(const json_t *, const char *);
char *		json_parse_str(const json_t *, const char *);
int		json_check_str(const json_t *, const char *, const char *);
int		json_parse_blob(const json_t *, const char *, void **, size_t *);
int		json_parse_allowcred(const json_t *, void **, size_t *);

#endif

A  => param.h +21 -0
@@ 1,21 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#define RP_ID			"demo.yubico.com"
#define TRANSPORT		"https://"
#define ORIGIN			TRANSPORT RP_ID

#define REGISTER_BEGIN_BODY	"{\"userVerification\":\"discouraged\"}"
#define REGISTER_BEGIN_URL	ORIGIN "/api/v1/simple/webauthn/register-begin"
#define REGISTER_FINISH_URL	ORIGIN "/api/v1/simple/webauthn/register-finish"

#define AUTH_BEGIN_BODY		"{\"userVerification\":\"discouraged\"}"
#define AUTH_BEGIN_URL		ORIGIN "/api/v1/simple/webauthn/authenticate-begin"
#define AUTH_FINISH_URL		ORIGIN "/api/v1/simple/webauthn/authenticate-finish"

#define MAX_RESPONSE_LEN	8192
#define MAX_CLIENTDATA_JSON_LEN	512
#define MIN_CHALLENGE_LEN	32

A  => webauthn-assert.c +338 -0
@@ 1,338 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#include <err.h>
#include <stdlib.h>
#include <string.h>

#include <cbor.h>
#include <fido.h>
#include <jansson.h>
#include <openssl/sha.h>

#include "base64.h"
#include "clientdata.h"
#include "http.h"
#include "json.h"
#include "param.h"
#include "webauthn-assert.h"

struct webauthn_assert {
	fido_assert_t		*assert;
	char			*clientdata;
	char			*request_id;
	struct http_post	*begin;
	struct http_post	*finish;
};

struct webauthn_assert *
webauthn_assert_new(void)
{
	struct webauthn_assert *wa;

	if ((wa = calloc(1, sizeof(*wa))) == NULL) {
		warnx("%s: calloc", __func__);
		return NULL;
	}

	if ((wa->assert = fido_assert_new()) == NULL) {
		warnx("%s: fido_assert_new", __func__);
		webauthn_assert_free(&wa);
	}

	return wa;
}

void
webauthn_assert_free(struct webauthn_assert **wap)
{
	struct webauthn_assert *wa;

	if (wap == NULL || (wa = *wap) == NULL)
		return;

	fido_assert_free(&wa->assert);
	free(wa->clientdata);
	free(wa->request_id);
	http_free(&wa->begin);
	http_free(&wa->finish);
	free(wa);
	*wap = NULL;
}

static int
webauthn_assert_begin_parse_response(struct webauthn_assert *wa)
{
	json_t	*blob;
	json_t	*data;
	json_t	*pubkey;
	char	*challenge = NULL;
	void	*cred_ptr = NULL;
	size_t	 cred_len;
	int	 ok = -1;

	if ((blob = http_response_json(wa->begin)) == NULL ||
	    json_check_str(blob, "status", "success") < 0 ||
	    (data = json_parse_obj(blob, "data")) == NULL) {
		warnx("%s: json", __func__);
		goto fail;
	}

	if ((pubkey = json_parse_obj(data, "publicKey")) == NULL ||
	    (wa->request_id = json_parse_str(data, "requestId")) == NULL) {
		warnx("%s: json data", __func__);
		goto fail;
	}

	if ((challenge = json_parse_str(pubkey, "challenge")) == NULL ||
	    json_check_str(pubkey, "rpId", RP_ID) < 0 ||
	    json_parse_allowcred(pubkey, &cred_ptr, &cred_len) < 0) {
		warnx("%s: json publicKey", __func__);
		goto fail;
	}

	if ((wa->clientdata = clientdata_json("webauthn.get",
	    challenge)) == NULL) {
		warnx("%s: json challenge", __func__);
		goto fail;
	}

	if (fido_assert_set_rp(wa->assert, RP_ID) != FIDO_OK) {
		warnx("%s: fido_assert_set_rp", __func__);
		goto fail;
	}

	if (fido_assert_allow_cred(wa->assert, cred_ptr, cred_len) != FIDO_OK) {
		warnx("%s: fido_assert_allow_cred", __func__);
		goto fail;
	}

	ok = 0;
fail:
	if (blob)
		json_decref(blob);

	free(challenge);
	free(cred_ptr);

	return ok;
}

int
webauthn_assert_begin(struct webauthn_assert *wa, const char *cookie)
{
	if (http_request(&wa->begin, AUTH_BEGIN_URL, AUTH_BEGIN_BODY,
	    cookie) < 0) {
		warnx("%s: http_request", __func__);
		return -1;
	}

	if (webauthn_assert_begin_parse_response(wa) < 0) {
		warnx("%s: webauthn_assert_begin_parse_response", __func__);
		return -1;
	}

	return 0;
}

int
webauthn_assert_get(struct webauthn_assert *wa, fido_dev_t *token)
{
	uint8_t	dgst[SHA256_DIGEST_LENGTH];
	int	status;

	if (SHA256((const uint8_t *)wa->clientdata, strlen(wa->clientdata),
	    dgst) != dgst) {
		warnx("%s: SHA256", __func__);
		return -1;
	}

	if (fido_assert_set_clientdata_hash(wa->assert, dgst,
	    sizeof(dgst)) != FIDO_OK) {
		warnx("%s: fido_assert_set_clientdata_hash", __func__);
		return -1;
	}

	if ((status = fido_dev_get_assert(token, wa->assert, NULL)) != FIDO_OK) {
		warnx("%s: fido_dev_get_assert: %s", __func__,
		    fido_strerr(status));
		return -1;
	}

	return 0;
}

static int
webauthn_assert_finish_parse_response(struct http_post *r)
{
	json_t	*blob;
	int	 ok = 0;

	if ((blob = http_response_json(r)) == NULL ||
	    json_check_str(blob, "status", "success") < 0) {
		warnx("%s: json", __func__);
		ok = -1;
	}

	if (blob)
		json_decref(blob);

	return ok;
}

static int
webauthn_assert_encode_authdata(const fido_assert_t *assert, char **out)
{
	cbor_item_t		*body = NULL;
	const unsigned char	*ptr;
	size_t			 len;
	int			 ok = -1;
	struct cbor_load_result	 cbor;

	*out = NULL;

	if ((body = cbor_load(fido_assert_authdata_ptr(assert, 0),
	    fido_assert_authdata_len(assert, 0), &cbor)) == NULL) {
		warnx("%s: cbor_load", __func__);
		goto fail;
	}

	if (cbor_isa_bytestring(body) == false ||
	    (ptr = cbor_bytestring_handle(body)) == NULL ||
	    (len = cbor_bytestring_length(body)) == 0) {
		warnx("%s: cbor_bytestring", __func__);
		goto fail;
	}

	if (base64_encode(ptr, len, out) < 0) {
		warnx("%s: base64_encode", __func__);
		goto fail;
	}

	ok = 0;
fail:
	if (body)
		cbor_decref(&body);

	return ok;
}

static int
webauthn_assert_encode_clientdata(const char *clientdata, char **out)
{
	if (base64_encode(clientdata, strlen(clientdata), out) < 0) {
		warnx("%s: base64_encode", __func__);
		return -1;
	}

	return 0;
}

static int
webauthn_assert_encode_cred_id(const fido_assert_t *assert, char **out)
{
	const void	*ptr = fido_assert_id_ptr(assert, 0);
	const size_t	 len = fido_assert_id_len(assert, 0);

	if (base64_encode(ptr, len, out) < 0) {
		warnx("%s: base64_encode", __func__);
		return -1;
	}

	return 0;
}

static int
webauthn_assert_encode_sig(const fido_assert_t *assert, char **out)
{
	const void	*ptr = fido_assert_sig_ptr(assert, 0);
	const size_t	 len = fido_assert_sig_len(assert, 0);

	if (base64_encode(ptr, len, out) < 0) {
		warnx("%s: base64_encode", __func__);
		return -1;
	}

	return 0;
}

static int
webauthn_assert_build_response(const struct webauthn_assert *wa, char **out)
{
	char	*authdata = NULL;
	char	*clientdata = NULL;
	char	*cred_id = NULL;
	char	*response = NULL;
	char	*sig = NULL;
	int	 n;
	int	 ok = -1;

	*out = NULL;

	if (webauthn_assert_encode_authdata(wa->assert, &authdata) < 0 ||
	    webauthn_assert_encode_clientdata(wa->clientdata, &clientdata) < 0 ||
	    webauthn_assert_encode_cred_id(wa->assert, &cred_id) < 0 ||
	    webauthn_assert_encode_sig(wa->assert, &sig) < 0) {
		warnx("%s: webauthn_assert_encode", __func__);
		goto fail;
	}

	if ((response = calloc(1, MAX_RESPONSE_LEN)) == NULL) {
		warnx("%s: calloc", __func__);
		goto fail;
	}

	n = snprintf(response, MAX_RESPONSE_LEN, "{\"requestId\":\"%s\","
	    "\"assertion\":{\"credentialId\":\"%s\",\"authenticatorData\":"
	    "\"%s\",\"clientDataJSON\":\"%s\",\"signature\":\"%s\"}}",
	    wa->request_id, cred_id, authdata, clientdata, sig);

	if (n < 0 || (size_t)n >= MAX_RESPONSE_LEN) {
		warnx("%s: snprintf", __func__);
		goto fail;
	}

	*out = response;
	response = NULL;
	ok = 0;
fail:
	free(authdata);
	free(clientdata);
	free(cred_id);
	free(response);
	free(sig);

	return ok;
}

int
webauthn_assert_finish(struct webauthn_assert *wa)
{
	char	*body = NULL;
	int	 ok = -1;

	if (webauthn_assert_build_response(wa, &body) < 0) {
		warnx("%s: webauthn_assert_build_response", __func__);
		goto fail;
	}

	if (http_request(&wa->finish, AUTH_FINISH_URL, body,
	    http_response_cookie(wa->begin)) < 0) {
		warnx("%s: http_request", __func__);
		goto fail;
	}

	if (webauthn_assert_finish_parse_response(wa->finish) < 0) {
		warnx("%s: webauthn_assert_finish_parse_response", __func__);
		goto fail;
	}

	ok = 0;
fail:
	free(body);

	return ok;
}

A  => webauthn-assert.h +18 -0
@@ 1,18 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#ifndef _WEBAUTHN_ASSERT_H_
#define _WEBAUTHN_ASSERT_H_

#include <fido.h>

struct webauthn_assert	*webauthn_assert_new(void);
void			 webauthn_assert_free(struct webauthn_assert **);
int			 webauthn_assert_begin(struct webauthn_assert *, const char *);
int			 webauthn_assert_get(struct webauthn_assert *, fido_dev_t *);
int			 webauthn_assert_finish(struct webauthn_assert *);

#endif

A  => webauthn-client.c +124 -0
@@ 1,124 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#include <err.h>
#include <fido.h>
#include <stdlib.h>
#include <string.h>

#include "clientdata.h"
#include "http.h"
#include "webauthn-assert.h"
#include "webauthn-cred.h"

static void
usage(void)
{
	fprintf(stderr, "usage: webauthn_client <dev>\n");
	exit(1);
}

static char *
registration(fido_dev_t *token)
{
	struct webauthn_cred	*wc;
	char			*cookie = NULL;

	if ((wc = webauthn_cred_new()) == NULL) {
		warnx("%s: webauthn_cred_new", __func__);
		goto fail;
	}

	if (webauthn_cred_begin(wc) < 0) {
		warnx("%s: webauthn_cred_begin", __func__);
		goto fail;
	}

	if (webauthn_cred_make_cred(wc, token) < 0) {
		warnx("%s: webauthn_cred_make_cred", __func__);
		goto fail;
	}

	if (webauthn_cred_finish(wc) < 0) {
		warnx("%s: webauthn_cred_finish", __func__);
		goto fail;
	}

	if ((cookie = strdup(webauthn_cred_session_cookie(wc))) == NULL) {
		warnx("%s: strdup", __func__);
		goto fail;
	}

fail:
	webauthn_cred_free(&wc);

	return cookie;

}

static int
authentication(fido_dev_t *token, const char *cookie)
{
	struct webauthn_assert	*wa;
	int			 ok = -1;

	if ((wa = webauthn_assert_new()) == NULL) {
		warnx("%s: webauthn_assert_new", __func__);
		goto fail;
	}

	if (webauthn_assert_begin(wa, cookie) < 0) {
		warnx("%s: webauthn_assert_begin", __func__);
		goto fail;
	}

	if (webauthn_assert_get(wa, token) < 0) {
		warnx("%s: webauthn_assert_get_assert", __func__);
		goto fail;
	}

	if (webauthn_assert_finish(wa) < 0) {
		warnx("%s: webauthn_assert_finish", __func__);
		goto fail;
	}

	ok = 0;
fail:
	webauthn_assert_free(&wa);

	return ok;
}

int
main(int argc, char **argv)
{
	fido_dev_t	*dev;
	char		*cookie;

	if (argc != 2)
		usage();

	fido_init(0);
	http_init();

	if ((dev = fido_dev_new()) == NULL)
		errx(1, "fido_dev_new");

	if (fido_dev_open(dev, argv[1]) != FIDO_OK)
		errx(1, "fido_dev_open");

	if ((cookie = registration(dev)) == NULL)
		errx(1, "%s: registration", __func__);

	if (authentication(dev, cookie) < 0)
		errx(1, "%s: authentication", __func__);

	fido_dev_close(dev);
	fido_dev_free(&dev);
	free(cookie);

	exit(0);
}

A  => webauthn-cred.c +309 -0
@@ 1,309 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#include <stdlib.h>
#include <string.h>
#include <err.h>

#include <fido.h>
#include <jansson.h>
#include <openssl/sha.h>

#include "base64.h"
#include "cbor.h"
#include "clientdata.h"
#include "http.h"
#include "json.h"
#include "param.h"
#include "webauthn-cred.h"

struct webauthn_cred {
	fido_cred_t		*cred;
	char			*clientdata;
	char			*request_id;
	struct http_post	*begin;
	struct http_post	*finish;
};

struct webauthn_cred *
webauthn_cred_new(void)
{
	struct webauthn_cred *wc;

	if ((wc = calloc(1, sizeof(*wc))) == NULL) {
		warnx("%s: calloc", __func__);
		return NULL;
	}

	if ((wc->cred = fido_cred_new()) == NULL) {
		warnx("%s: fido_cred_new", __func__);
		webauthn_cred_free(&wc);
	}

	return wc;
}

void
webauthn_cred_free(struct webauthn_cred **wcp)
{
	struct webauthn_cred *wc;

	if (wcp == NULL || (wc = *wcp) == NULL)
		return;

	fido_cred_free(&wc->cred);
	free(wc->clientdata);
	free(wc->request_id);
	http_free(&wc->begin);
	http_free(&wc->finish);
	free(wc);
	*wcp = NULL;
}

static int
webauthn_cred_begin_parse_response(struct webauthn_cred *wc)
{
	json_t	*blob;
	json_t	*data;
	json_t	*pubkey;
	json_t	*user;
	json_t	*rp;
	char	*challenge = NULL;
	char	*display_name = NULL;
	char	*rp_name = NULL;
	char	*user_name = NULL;
	void	*user_id_ptr = NULL;
	size_t	 user_id_len;
	int	 ok = -1;

	if ((blob = http_response_json(wc->begin)) == NULL ||
	    json_check_str(blob, "status", "success") < 0 ||
	    (data = json_parse_obj(blob, "data")) == NULL) {
		warnx("%s: json", __func__);
		goto fail;
	}

	if ((pubkey = json_parse_obj(data, "publicKey")) == NULL ||
	    (wc->request_id = json_parse_str(data, "requestId")) == NULL) {
		warnx("%s: json data", __func__);
		goto fail;
	}

	if ((challenge = json_parse_str(pubkey, "challenge")) == NULL ||
	    (user = json_parse_obj(pubkey, "user")) == NULL ||
	    (rp = json_parse_obj(pubkey, "rp")) == NULL) {
		warnx("%s: json publicKey", __func__);
		goto fail;
	}

	if (json_parse_blob(user, "id", &user_id_ptr, &user_id_len) < 0 ||
	    (user_name = json_parse_str(user, "name")) == NULL ||
	    (display_name = json_parse_str(user, "displayName")) == NULL) {
		warnx("%s: json user", __func__);
		goto fail;
	}

	if (json_check_str(rp, "id", RP_ID) < 0 ||
	    (rp_name = json_parse_str(rp, "name")) == NULL) {
		warnx("%s: json rp", __func__);
		goto fail;
	}

	if ((wc->clientdata = clientdata_json("webauthn.create",
	    challenge)) == NULL) {
		warnx("%s: json", __func__);
		goto fail;
	}

	if (fido_cred_set_rp(wc->cred, RP_ID, rp_name) != FIDO_OK) {
		warnx("%s: fido_cred_set_rp", __func__);
		goto fail;
	}

	if (fido_cred_set_user(wc->cred, user_id_ptr, user_id_len, user_name,
	    display_name, NULL) != FIDO_OK) {
		warnx("%s: fido_cred_set_user", __func__);
		goto fail;
	}

	ok = 0;
fail:
	if (blob)
		json_decref(blob);

	free(challenge);
	free(display_name);
	free(rp_name);
	free(user_name);
	free(user_id_ptr);

	return ok;
}

int
webauthn_cred_begin(struct webauthn_cred *wc)
{
	if (http_request(&wc->begin, REGISTER_BEGIN_URL, REGISTER_BEGIN_BODY,
	    NULL) < 0) {
		warnx("%s: http_request", __func__);
		return -1;
	}

	if (webauthn_cred_begin_parse_response(wc) < 0) {
		warnx("%s: webauthn_cred_begin_parse_response", __func__);
		return -1;
	}

	return 0;
}

int
webauthn_cred_make_cred(struct webauthn_cred *wc, fido_dev_t *token)
{
	uint8_t	dgst[SHA256_DIGEST_LENGTH];
	int	status;

	if (SHA256((const uint8_t *)wc->clientdata, strlen(wc->clientdata),
	    dgst) != dgst) {
		warnx("%s: SHA256", __func__);
		return -1;
	}

	if (fido_cred_set_clientdata_hash(wc->cred, dgst,
	    sizeof(dgst)) != FIDO_OK) {
		warnx("%s: fido_cred_set_clientdata_hash", __func__);
		return -1;
	}

	if (fido_cred_set_type(wc->cred, COSE_ES256) != FIDO_OK) {
		warnx("%s: fido_cred_set_type", __func__);
		return -1;
	}

	status = fido_dev_make_cred(token, wc->cred, NULL);

	if (status == FIDO_ERR_PIN_REQUIRED) {
		fido_dev_force_u2f(token);
		status = fido_dev_make_cred(token, wc->cred, NULL);
	}

	if (status != FIDO_OK) {
		warnx("%s: fido_dev_make_cred: %s", __func__,
		    fido_strerr(status));
		return -1;
	}

	return 0;
}

static int
webauthn_cred_finish_parse_response(struct http_post *wc)
{
	json_t	*blob;
	int	 ok = 0;

	if ((blob = http_response_json(wc)) == NULL ||
	    json_check_str(blob, "status", "success") < 0) {
		warnx("%s: json", __func__);
		ok = -1;
	}

	if (blob)
		json_decref(blob);

	return ok;
}

static int
webauthn_cred_build_response(const struct webauthn_cred *wc, char **out)
{
	char	*attobj = NULL;
	char	*clientdata = NULL;
	char	*response = NULL;
	int	 n;
	int	 ok = -1;

	*out = NULL;

	if (fido_cred_user_name(wc->cred) == NULL ||
	    fido_cred_display_name(wc->cred) == NULL) {
		warnx("%s: fido_cred", __func__);
		goto fail;
	}

	if ((attobj = cbor_build_attestation_object(wc->cred)) == NULL) {
		warnx("%s: cbor_build_attestation_object", __func__);
		goto fail;
	}

	if (base64_encode(wc->clientdata, strlen(wc->clientdata),
	    &clientdata) < 0) {
		warnx("%s: base64_encode_str" , __func__);
		goto fail;
	}

	if ((response = calloc(1, MAX_RESPONSE_LEN)) == NULL) {
		warnx("%s: calloc", __func__);
		goto fail;
	}

	n = snprintf(response, MAX_RESPONSE_LEN, "{\"requestId\":\"%s\","
	    "\"username\":\"%s\",\"displayName\":\"%s\",\"icon\":null,"
	    "\"attestation\":{\"attestationObject\":\"%s\",\""
	    "clientDataJSON\":\"%s\"}}", wc->request_id,
	    fido_cred_user_name(wc->cred), fido_cred_display_name(wc->cred),
	    attobj, clientdata);

	if (n < 0 || (size_t)n >= MAX_RESPONSE_LEN) {
		warnx("%s: snprintf", __func__);
		goto fail;
	}

	*out = response;
	response = NULL;
	ok = 0;
fail:
	free(attobj);
	free(clientdata);
	free(response);

	return ok;
}

int
webauthn_cred_finish(struct webauthn_cred *wc)
{
	char	*body = NULL;
	int	 ok = -1;

	if (webauthn_cred_build_response(wc, &body) < 0) {
		warnx("%s: webauthn_cred_build_response", __func__);
		goto fail;
	}

	if (http_request(&wc->finish, REGISTER_FINISH_URL, body,
	    http_response_cookie(wc->begin)) < 0) {
		warnx("%s: http_request", __func__);
		goto fail;
	}

	if (webauthn_cred_finish_parse_response(wc->finish) < 0) {
		warnx("%s: webauthn_cred_finish_parse_response", __func__);
		goto fail;
	}

	ok = 0;
fail:
	free(body);

	return ok;
}

const char *
webauthn_cred_session_cookie(const struct webauthn_cred *wc)
{
	return http_response_cookie(wc->finish);
}

A  => webauthn-cred.h +17 -0
@@ 1,17 @@
/*
 * Copyright (c) 2020 Pedro Martelletto. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#ifndef _WEBAUTHN_CRED_H_
#define _WEBAUTHN_CRED_H_

struct webauthn_cred	*webauthn_cred_new(void);
void			 webauthn_cred_free(struct webauthn_cred **);
int			 webauthn_cred_begin(struct webauthn_cred *);
int			 webauthn_cred_make_cred(struct webauthn_cred *, fido_dev_t *);
int			 webauthn_cred_finish(struct webauthn_cred *);
const char		*webauthn_cred_session_cookie(const struct webauthn_cred *);

#endif