/* * lws-minimal-crypto-cose-sign * * Written in 2010-2021 by Andy Green * * This file is made available under the Creative Commons CC0 1.0 * Universal Public Domain Dedication. */ #include #include #include static int fdin = 0, fdout = 1; static uint8_t extra[4096]; static size_t ext_len; int _alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, size_t *amount) { FILE *f; size_t s; ssize_t m; int n = 0; f = fopen(filename, "rb"); if (f == NULL) { n = 1; goto bail; } if (fseek(f, 0, SEEK_END) != 0) { n = 1; goto bail; } m = ftell(f); if (m == -1l) { n = 1; goto bail; } s = (size_t)m; if (fseek(f, 0, SEEK_SET) != 0) { n = 1; goto bail; } *buf = malloc(s + 1); if (!*buf) { n = 2; goto bail; } if (fread(*buf, s, 1, f) != 1) { free(*buf); n = 1; goto bail; } *amount = s; bail: if (f) fclose(f); return n; } static int extra_cb(lws_cose_sig_ext_pay_t *x) { x->ext = extra; x->xl = ext_len; // lwsl_hexdump_notice(extra, ext_len); return 0; } int pay_cb(struct lws_cose_validate_context *cps, void *opaque, const uint8_t *paychunk, size_t paychunk_len) { write(fdout, paychunk, paychunk_len); return 0; } int main(int argc, const char **argv) { uint8_t *ks, temp[256], *kid = NULL, ktmp[4096], sbuf[512]; int n, m, sign = 0, result = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; enum lws_cose_sig_types sigtype = SIGTYPE_UNKNOWN; struct lws_cose_validate_context *cps = NULL; struct lws_cose_sign_context *csc = NULL; const struct lws_gencrypto_keyelem *ke; struct lws_context_creation_info info; lws_cose_validate_create_info_t vi; struct lws_buflist *paybuf = NULL; lws_cose_sign_create_info_t i; struct lws_context *context; size_t ks_len, kid_len = 0; lws_cose_key_t *ck = NULL; lws_dll2_owner_t *o, set; lws_lec_pctx_t lec; cose_param_t alg; const char *p; if ((p = lws_cmdline_option(argc, argv, "-d"))) logs = atoi(p); lws_set_log_level(logs, NULL); lwsl_user("LWS cose-sign example tool -k keyset [-s alg-name kid ]\n"); memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ #if defined(LWS_WITH_NETWORK) info.port = CONTEXT_PORT_NO_LISTEN; #endif context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n"); return 1; } if ((p = lws_cmdline_option(argc, argv, "--stdin"))) { fdin = open(p, LWS_O_RDONLY, 0); if (fdin < 0) { lwsl_err("%s: unable to open stdin file\n", __func__); return 1; } } if ((p = lws_cmdline_option(argc, argv, "--stdout"))) { fdout = open(p, LWS_O_WRONLY | LWS_O_CREAT | LWS_O_TRUNC, 0600); if (fdout < 0) { lwsl_err("%s: unable to open stdout file\n", __func__); goto bail_early; } } /* * If no tag, you can tell it the signature type, otherwise it will * use the tag to select the right type without these */ if (lws_cmdline_option(argc, argv, "--cose-sign")) sigtype = SIGTYPE_MULTI; if (lws_cmdline_option(argc, argv, "--cose-sign1")) sigtype = SIGTYPE_SINGLE; if (lws_cmdline_option(argc, argv, "--cose-mac")) sigtype = SIGTYPE_MAC; if (lws_cmdline_option(argc, argv, "--cose-mac0")) sigtype = SIGTYPE_MAC0; /* if signing, set the ciphers */ if (lws_cmdline_option(argc, argv, "-s")) sign = 1; if ((p = lws_cmdline_option(argc, argv, "--kid"))) { kid = (uint8_t *)p; kid_len = strlen(p); //lwsl_hexdump_notice(kid, kid_len); } if ((p = lws_cmdline_option(argc, argv, "--kid-hex"))) { kid_len = (size_t)lws_hex_to_byte_array(p, ktmp, sizeof(ktmp)); kid = (uint8_t *)ktmp; } if ((p = lws_cmdline_option(argc, argv, "--extra"))) { ext_len = (size_t)lws_hex_to_byte_array(p, extra, sizeof(extra)); lwsl_notice("%llu\n", (unsigned long long)ext_len); if (ext_len == (size_t)-1ll) ext_len = 0; } /* grab the key */ if (!(p = lws_cmdline_option(argc, argv, "-k"))) { lwsl_err("-k is required\n"); goto bail; } if (_alloc_file(context, p, &ks, &ks_len)) { lwsl_err("%s: unable to load %s\n", __func__, p); goto bail; } lws_dll2_owner_clear(&set); if (!lws_cose_key_import(&set, NULL, NULL, ks, ks_len)) { lwsl_notice("%s: key import fail\n", __func__); free(ks); goto bail2; } free(ks); if (!fdin) { struct timeval timeout; fd_set fds; FD_ZERO(&fds); FD_SET(0, &fds); timeout.tv_sec = 0; timeout.tv_usec = 1000; if (select(fdin + 1, &fds, NULL, NULL, &timeout) < 0 || !FD_ISSET(0, &fds)) { lwsl_err("%s: pass cose_sign or plaintext " "on stdin or --stdin\n", __func__); goto bail2; } } if (sign) { uint8_t *ppay; size_t s; p = lws_cmdline_option(argc, argv, "--alg"); if (!p) { lwsl_err("%s: need to specify alg (eg, ES256) " "when signing\n", __func__); goto bail2; } alg = lws_cose_name_to_alg(p); lws_lec_init(&lec, sbuf, sizeof(sbuf)); memset(&i, 0, sizeof(i)); i.cx = context; i.keyset = &set; i.lec = &lec; i.flags = LCSC_FL_ADD_CBOR_TAG | LCSC_FL_ADD_CBOR_PREFER_MAC0; i.sigtype = sigtype; /* * Unfortunately, with COSE we must know the payload length * before we have seen the payload. It's illegal to use * indeterminite lengths inside COSE objects. */ do { n = (int)read(fdin, temp, sizeof(temp)); if (n < 0) goto bail3; if (!n) break; s = (size_t)n; if (lws_buflist_append_segment(&paybuf, temp, s) < 0) goto bail3; i.inline_payload_len += s; } while (1); // lwsl_notice("%s: inline_payload_len %llu\n", __func__, // (unsigned long long)i.inline_payload_len); csc = lws_cose_sign_create(&i); if (!csc) goto bail2; ck = lws_cose_key_from_set(&set, kid, kid_len); if (!ck) goto bail2; if (lws_cose_sign_add(csc, alg, ck)) goto bail2; do { s = lws_buflist_next_segment_len(&paybuf, &ppay); if (!s) break; do { m = (int)lws_cose_sign_payload_chunk(csc, ppay, s); if (lec.used) { // lwsl_hexdump_err(sbuf, lec.used); write(fdout, sbuf, lec.used); lws_lec_setbuf(&lec, sbuf, sizeof(sbuf)); } } while (m == LCOSESIGEXTCB_RET_AGAIN); if (m == LWS_LECPCTX_RET_FAIL) goto bail2; if (lec.used) { write(fdout, sbuf, lec.used); lws_lec_setbuf(&lec, sbuf, sizeof(sbuf)); } lws_buflist_use_segment(&paybuf, s); } while(1); } else { memset(&vi, 0, sizeof(vi)); vi.cx = context; vi.keyset = &set; vi.sigtype = sigtype; vi.ext_cb = extra_cb; vi.ext_opaque = extra; vi.ext_len = ext_len; vi.pay_cb = pay_cb; cps = lws_cose_validate_create(&vi); if (!cps) { lwsl_notice("%s: sign_val_create fail\n", __func__); goto bail; } do { n = (int)read(fdin, temp, sizeof(temp)); if (n < 0) goto bail3; if (!n) break; n = lws_cose_validate_chunk(cps, temp, (size_t)n, NULL); if (n && n != LECP_CONTINUE) { lwsl_err("%s: chunk validation failed: %d\n", __func__, n); goto bail2; } } while (1); } bail3: result = 0; if (!sign) { char buf[2048]; int os; o = lws_cose_validate_results(cps); if (!o) result = 1; else { os = lws_snprintf(buf, sizeof(buf), "\nresults count %d\n", o->count); write(fdout, buf, (size_t)os); if (!o->count) result = 1; } lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp, lws_dll2_get_head(o)) { lws_cose_validate_res_t *res = lws_container_of(p, lws_cose_validate_res_t, list); char khr[256]; khr[0] = '\0'; if (res->cose_key) { ke = &res->cose_key->meta[COSEKEY_META_KID]; if (ke && ke->buf) lws_hex_from_byte_array(ke->buf, ke->len, khr, sizeof(khr)); } os = lws_snprintf(buf, sizeof(buf), " result: %d (alg %s, kid %s)\n", res->result, lws_cose_alg_to_name(res->cose_alg), khr); write(fdout, buf, (size_t)os); result |= res->result; } lws_end_foreach_dll_safe(p, tp); } bail2: if (!sign) lws_cose_validate_destroy(&cps); else { lws_buflist_destroy_all_segments(&paybuf); lws_cose_sign_destroy(&csc); } //bail1: lws_cose_key_set_destroy(&set); bail: lws_context_destroy(context); if (result) lwsl_err("%s: FAIL: %d\n", __func__, result); else lwsl_notice("%s: PASS\n", __func__); bail_early: if (fdin > 0) close(fdin); if (fdout != 1 && fdout >= 0) close(fdout); return result; }