From: Oleg Moiseenko <807634+merlokk@users.noreply.github.com> Date: Fri, 7 Dec 2018 15:42:37 +0000 (+0200) Subject: Fido2 (#727) X-Git-Url: https://git.zerfleddert.de/cgi-bin/gitweb.cgi/proxmark3-svn/commitdiff_plain/0bb514502a1d80e9b023d0e8a379f3559798eec2?hp=27d06e044795c0b0ea4d34b10bccfa41d57f77fc Fido2 (#727) * add tinycbor * add client/fido * add test file with options for fido2 * hf fido commands * add changelog --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d8993dc..e2ed8878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Added `hf fido` - FIDO U2F authenticator commands https://fidoalliance.org/ (Merlok) - Added `lf hitag reader 03` - read block (instead of pages) - Added `lf hitag reader 04` - read block (instead of pages) +- Added `hf fido` `assert` and `make` commands from fido2 protocol (authenticatorMakeCredential and authenticatorGetAssertion) (Merlok) ## [v3.1.0][2018-10-10] diff --git a/client/Makefile b/client/Makefile index 5ed4ea49..9ad8efdd 100644 --- a/client/Makefile +++ b/client/Makefile @@ -23,8 +23,12 @@ JANSSONLIBPATH = ./jansson JANSSONLIB = $(JANSSONLIBPATH)/libjansson.a MBEDTLSLIBPATH = ../common/mbedtls MBEDTLSLIB = $(MBEDTLSLIBPATH)/libmbedtls.a +CBORLIBPATH = ./tinycbor +CBORLIB = $(CBORLIBPATH)/tinycbor.a +LIBINCLUDES = -I../zlib -I../uart -I../liblua -I$(MBEDTLSLIBPATH) -I$(JANSSONLIBPATH) -I$(CBORLIBPATH) +INCLUDES_CLIENT = -I. -I../include -I../common -I/opt/local/include $(LIBINCLUDES) LDFLAGS = $(ENV_LDFLAGS) -CFLAGS = $(ENV_CFLAGS) -std=c99 -D_ISOC99_SOURCE -I. -I../include -I../common -I../common/polarssl -I../zlib -I../uart -I/opt/local/include -I../liblua -I$(JANSSONLIBPATH) -I$(MBEDTLSLIBPATH) -Wall -g -O3 +CFLAGS = $(ENV_CFLAGS) -std=c99 -D_ISOC99_SOURCE $(INCLUDES_CLIENT) -Wall -g -O3 CXXFLAGS = -I../include -Wall -O3 APP_CFLAGS = @@ -113,7 +117,10 @@ CMDSRCS = $(SRC_SMARTCARD) \ cliparser/argtable3.c\ cliparser/cliparser.c\ fido/additional_ca.c \ - mfkey.c\ + fido/cose.c \ + fido/cbortools.c \ + fido/fidocore.c \ + mfkey.c \ loclass/cipher.c \ loclass/cipherutils.c \ loclass/ikeys.c \ @@ -246,12 +253,12 @@ WINBINS = $(patsubst %, %.exe, $(BINS)) CLEAN = $(BINS) $(WINBINS) $(COREOBJS) $(CMDOBJS) $(OBJCOBJS) $(ZLIBOBJS) $(QTGUIOBJS) $(MULTIARCHOBJS) $(OBJDIR)/*.o *.moc.cpp ui/ui_overlays.h # need to assign dependancies to build these first... -all: lua_build jansson_build mbedtls_build $(BINS) +all: lua_build jansson_build mbedtls_build cbor_build $(BINS) all-static: LDLIBS:=-static $(LDLIBS) all-static: proxmark3 flasher fpga_compress -proxmark3: LDLIBS+=$(LUALIB) $(JANSSONLIB) $(MBEDTLSLIB) $(QTLDLIBS) +proxmark3: LDLIBS+=$(LUALIB) $(JANSSONLIB) $(MBEDTLSLIB) $(CBORLIB) $(QTLDLIBS) proxmark3: $(OBJDIR)/proxmark3.o $(COREOBJS) $(CMDOBJS) $(OBJCOBJS) $(QTGUIOBJS) $(MULTIARCHOBJS) $(ZLIBOBJS) lualibs/usb_cmd.lua $(LD) $(LDFLAGS) $(OBJDIR)/proxmark3.o $(COREOBJS) $(CMDOBJS) $(OBJCOBJS) $(QTGUIOBJS) $(MULTIARCHOBJS) $(ZLIBOBJS) $(LDLIBS) -o $@ @@ -275,8 +282,9 @@ lualibs/usb_cmd.lua: ../include/usb_cmd.h clean: $(RM) $(CLEAN) cd ../liblua && make clean - cd ./jansson && make clean + cd $(JANSSONLIBPATH) && make clean cd $(MBEDTLSLIBPATH) && make clean + cd $(CBORLIBPATH) && make clean tarbin: $(BINS) $(TAR) $(TARFLAGS) ../proxmark3-$(platform)-bin.tar $(BINS:%=client/%) $(WINBINS:%=client/%) @@ -292,7 +300,11 @@ jansson_build: mbedtls_build: @echo Compiling mbedtls cd $(MBEDTLSLIBPATH) && make all - + +cbor_build: + @echo Compiling tinycbor + cd $(CBORLIBPATH) && make all + .PHONY: all clean $(OBJDIR)/%_NOSIMD.o : %.c $(OBJDIR)/%.d diff --git a/client/cmdhffido.c b/client/cmdhffido.c index 357e265c..9c812860 100644 --- a/client/cmdhffido.c +++ b/client/cmdhffido.c @@ -28,12 +28,14 @@ #include #include #include +#include +#include +#include #include "comms.h" #include "cmdmain.h" #include "util.h" #include "ui.h" #include "proxmark3.h" -#include "cmdhf14a.h" #include "mifare.h" #include "emv/emvcore.h" #include "emv/emvjson.h" @@ -41,50 +43,12 @@ #include "cliparser/cliparser.h" #include "crypto/asn1utils.h" #include "crypto/libpcrypto.h" -#include "fido/additional_ca.h" -#include "mbedtls/x509_crt.h" -#include "mbedtls/x509.h" -#include "mbedtls/pk.h" +#include "fido/cbortools.h" +#include "fido/fidocore.h" +#include "fido/cose.h" static int CmdHelp(const char *Cmd); -int FIDOSelect(bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { - uint8_t data[] = {0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01}; - - return EMVSelect(ActivateField, LeaveFieldON, data, sizeof(data), Result, MaxResultLen, ResultLen, sw, NULL); -} - -int FIDOExchange(sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { - int res = EMVExchange(true, apdu, Result, MaxResultLen, ResultLen, sw, NULL); - if (res == 5) // apdu result (sw) not a 0x9000 - res = 0; - // software chaining - while (!res && (*sw >> 8) == 0x61) { - size_t oldlen = *ResultLen; - res = EMVExchange(true, (sAPDU){0x00, 0xC0, 0x00, 0x00, 0x00, NULL}, &Result[oldlen], MaxResultLen - oldlen, ResultLen, sw, NULL); - if (res == 5) // apdu result (sw) not a 0x9000 - res = 0; - - *ResultLen += oldlen; - if (*ResultLen > MaxResultLen) - return 100; - } - return res; -} - -int FIDORegister(uint8_t *params, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { - return FIDOExchange((sAPDU){0x00, 0x01, 0x03, 0x00, 64, params}, Result, MaxResultLen, ResultLen, sw); -} - -int FIDOAuthentication(uint8_t *params, uint8_t paramslen, uint8_t controlb, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { - return FIDOExchange((sAPDU){0x00, 0x02, controlb, 0x00, paramslen, params}, Result, MaxResultLen, ResultLen, sw); -} - -int FIDO2GetInfo(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { - uint8_t data[] = {0x04}; - return FIDOExchange((sAPDU){0x80, 0x10, 0x00, 0x00, sizeof(data), data}, Result, MaxResultLen, ResultLen, sw); -} - int CmdHFFidoInfo(const char *cmd) { if (cmd && strlen(cmd) > 0) @@ -139,9 +103,23 @@ int CmdHFFidoInfo(const char *cmd) { return 0; } + + if(buf[0]) { + PrintAndLog("FIDO2 ger version error: %d - %s", buf[0], fido2GetCmdErrorDescription(buf[0])); + return 0; + } - PrintAndLog("FIDO2 version: (%d)", len); - dump_buffer((const unsigned char *)buf, len, NULL, 0); + if (len > 1) { +// if (false) { +// PrintAndLog("FIDO2 version: (len=%d)", len); +// dump_buffer((const unsigned char *)buf, len, NULL, 0); +// } + + PrintAndLog("FIDO2 version CBOR decoded:"); + TinyCborPrintFIDOPackage(fido2CmdGetInfo, true, &buf[1], len - 1); + } else { + PrintAndLog("FIDO2 version length error"); + } return 0; } @@ -348,60 +326,7 @@ int CmdHFFidoRegister(const char *cmd) { PrintAndLog("----------------DER TLV-----------------"); } - // load CA's - mbedtls_x509_crt cacert; - mbedtls_x509_crt_init(&cacert); - res = mbedtls_x509_crt_parse(&cacert, (const unsigned char *) additional_ca_pem, additional_ca_pem_len); - if (res < 0) { - PrintAndLog("ERROR: CA parse certificate returned -0x%x - %s", -res, ecdsa_get_error(res)); - } - if (verbose) - PrintAndLog("CA load OK. %d skipped", res); - - // load DER certificate from authenticator's data - mbedtls_x509_crt cert; - mbedtls_x509_crt_init(&cert); - res = mbedtls_x509_crt_parse_der(&cert, &buf[derp], derLen); - if (res) { - PrintAndLog("ERROR: DER parse returned 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res)); - } - - // get certificate info - char linfo[300] = {0}; - if (verbose) { - mbedtls_x509_crt_info(linfo, sizeof(linfo), " ", &cert); - PrintAndLog("DER certificate info:\n%s", linfo); - } - - // verify certificate - uint32_t verifyflags = 0; - res = mbedtls_x509_crt_verify(&cert, &cacert, NULL, NULL, &verifyflags, NULL, NULL); - if (res) { - PrintAndLog("ERROR: DER verify returned 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res)); - } else { - PrintAndLog("Certificate OK."); - } - - if (verbose) { - memset(linfo, 0x00, sizeof(linfo)); - mbedtls_x509_crt_verify_info(linfo, sizeof(linfo), " ", verifyflags); - PrintAndLog("Verification info:\n%s", linfo); - } - - // get public key - res = ecdsa_public_key_from_pk(&cert.pk, public_key, sizeof(public_key)); - if (res) { - PrintAndLog("ERROR: getting public key from certificate 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res)); - } else { - if (verbose) - PrintAndLog("Got a public key from certificate:\n%s", sprint_hex_inrow(public_key, 65)); - } - - if (verbose) - PrintAndLog("------------------DER-------------------"); - - mbedtls_x509_crt_free(&cert); - mbedtls_x509_crt_free(&cacert); + FIDOCheckDERAndGetKey(&buf[derp], derLen, verbose, public_key, sizeof(public_key)); // get hash int hashp = 1 + 65 + 1 + keyHandleLen + derLen; @@ -690,12 +615,301 @@ int CmdHFFidoAuthenticate(const char *cmd) { return 0; }; +void CheckSlash(char *fileName) { + if ((fileName[strlen(fileName) - 1] != '/') && + (fileName[strlen(fileName) - 1] != '\\')) + strcat(fileName, "/"); +} + +int GetExistsFileNameJson(char *prefixDir, char *reqestedFileName, char *fileName) { + fileName[0] = 0x00; + strcpy(fileName, get_my_executable_directory()); + CheckSlash(fileName); + + strcat(fileName, prefixDir); + CheckSlash(fileName); + + strcat(fileName, reqestedFileName); + if (!strstr(fileName, ".json")) + strcat(fileName, ".json"); + + if (access(fileName, F_OK) < 0) { + strcpy(fileName, get_my_executable_directory()); + CheckSlash(fileName); + + strcat(fileName, reqestedFileName); + if (!strstr(fileName, ".json")) + strcat(fileName, ".json"); + + if (access(fileName, F_OK) < 0) { + return 1; // file not found + } + } + return 0; +} + +int CmdHFFido2MakeCredential(const char *cmd) { + json_error_t error; + json_t *root = NULL; + char fname[300] = {0}; + + CLIParserInit("hf fido make", + "Execute a FIDO2 Make Credentional command. Needs json file with parameters. Sample file `fido2.json`. File can be placed in proxmark directory or in `proxmark/fido` directory.", + "Usage:\n\thf fido make -> execute command default parameters file `fido2.json`\n" + "\thf fido make test.json -> execute command with parameters file `text.json`"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_litn("vV", "verbose", 0, 2, "show technical data. vv - show full certificates data"), + arg_lit0("tT", "tlv", "Show DER certificate contents in TLV representation"), + arg_lit0("cC", "cbor", "show CBOR decoded data"), + arg_str0(NULL, NULL, "", "JSON input / output file name for parameters. Default `fido2.json`"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool APDULogging = arg_get_lit(1); + bool verbose = arg_get_lit(2); + bool verbose2 = arg_get_lit(2) > 1; + bool showDERTLV = arg_get_lit(3); + bool showCBOR = arg_get_lit(4); + + uint8_t jsonname[250] ={0}; + char *cjsonname = (char *)jsonname; + int jsonnamelen = 0; + CLIGetStrWithReturn(5, jsonname, &jsonnamelen); + + if (!jsonnamelen) { + strcat(cjsonname, "fido2"); + jsonnamelen = strlen(cjsonname); + } + + CLIParserFree(); + + SetAPDULogging(APDULogging); + + int res = GetExistsFileNameJson("fido", cjsonname, fname); + if(res) { + PrintAndLog("ERROR: Can't found the json file."); + return res; + } + PrintAndLog("fname: %s\n", fname); + root = json_load_file(fname, 0, &error); + if (!root) { + PrintAndLog("ERROR: json error on line %d: %s", error.line, error.text); + return 1; + } + + uint8_t data[2048] = {0}; + size_t datalen = 0; + uint8_t buf[2048] = {0}; + size_t len = 0; + uint16_t sw = 0; + + DropField(); + res = FIDOSelect(true, true, buf, sizeof(buf), &len, &sw); + + if (res) { + PrintAndLog("Can't select authenticator. res=%x. Exit...", res); + DropField(); + return res; + } + + if (sw != 0x9000) { + PrintAndLog("Can't select FIDO application. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + DropField(); + return 2; + } + + res = FIDO2CreateMakeCredentionalReq(root, data, sizeof(data), &datalen); + if (res) + return res; + + if (showCBOR) { + PrintAndLog("CBOR make credentional request:"); + PrintAndLog("---------------- CBOR ------------------"); + TinyCborPrintFIDOPackage(fido2CmdMakeCredential, false, data, datalen); + PrintAndLog("---------------- CBOR ------------------"); + } + + res = FIDO2MakeCredential(data, datalen, buf, sizeof(buf), &len, &sw); + DropField(); + if (res) { + PrintAndLog("Can't execute make credential command. res=%x. Exit...", res); + return res; + } + + if (sw != 0x9000) { + PrintAndLog("ERROR execute make credential command. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + return 3; + } + + if(buf[0]) { + PrintAndLog("FIDO2 make credential error: %d - %s", buf[0], fido2GetCmdErrorDescription(buf[0])); + return 0; + } + + PrintAndLog("MakeCredential result (%d b) OK.", len); + if (showCBOR) { + PrintAndLog("CBOR make credentional response:"); + PrintAndLog("---------------- CBOR ------------------"); + TinyCborPrintFIDOPackage(fido2CmdMakeCredential, true, &buf[1], len - 1); + PrintAndLog("---------------- CBOR ------------------"); + } + + // parse returned cbor + FIDO2MakeCredentionalParseRes(root, &buf[1], len - 1, verbose, verbose2, showCBOR, showDERTLV); + + if (root) { + res = json_dump_file(root, fname, JSON_INDENT(2)); + if (res) { + PrintAndLog("ERROR: can't save the file: %s", fname); + return 200; + } + PrintAndLog("File `%s` saved.", fname); + } + + json_decref(root); + + return 0; +}; + +int CmdHFFido2GetAssertion(const char *cmd) { + json_error_t error; + json_t *root = NULL; + char fname[300] = {0}; + + CLIParserInit("hf fido assert", + "Execute a FIDO2 Get Assertion command. Needs json file with parameters. Sample file `fido2.json`. File can be placed in proxmark directory or in `proxmark/fido` directory.", + "Usage:\n\thf fido assert -> execute command default parameters file `fido2.json`\n" + "\thf fido assert test.json -l -> execute command with parameters file `text.json` and add to request CredentialId"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_litn("vV", "verbose", 0, 2, "show technical data. vv - show full certificates data"), + arg_lit0("cC", "cbor", "show CBOR decoded data"), + arg_lit0("lL", "list", "add CredentialId from json to allowList. Needs if `rk` option is `false` (authenticator don't store credential to its memory)"), + arg_str0(NULL, NULL, "", "JSON input / output file name for parameters. Default `fido2.json`"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool APDULogging = arg_get_lit(1); + bool verbose = arg_get_lit(2); + bool verbose2 = arg_get_lit(2) > 1; + bool showCBOR = arg_get_lit(3); + bool createAllowList = arg_get_lit(4); + + uint8_t jsonname[250] ={0}; + char *cjsonname = (char *)jsonname; + int jsonnamelen = 0; + CLIGetStrWithReturn(5, jsonname, &jsonnamelen); + + if (!jsonnamelen) { + strcat(cjsonname, "fido2"); + jsonnamelen = strlen(cjsonname); + } + + CLIParserFree(); + + SetAPDULogging(APDULogging); + + int res = GetExistsFileNameJson("fido", "fido2", fname); + if(res) { + PrintAndLog("ERROR: Can't found the json file."); + return res; + } + PrintAndLog("fname: %s\n", fname); + root = json_load_file(fname, 0, &error); + if (!root) { + PrintAndLog("ERROR: json error on line %d: %s", error.line, error.text); + return 1; + } + + uint8_t data[2048] = {0}; + size_t datalen = 0; + uint8_t buf[2048] = {0}; + size_t len = 0; + uint16_t sw = 0; + + DropField(); + res = FIDOSelect(true, true, buf, sizeof(buf), &len, &sw); + + if (res) { + PrintAndLog("Can't select authenticator. res=%x. Exit...", res); + DropField(); + return res; + } + + if (sw != 0x9000) { + PrintAndLog("Can't select FIDO application. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + DropField(); + return 2; + } + + res = FIDO2CreateGetAssertionReq(root, data, sizeof(data), &datalen, createAllowList); + if (res) + return res; + + if (showCBOR) { + PrintAndLog("CBOR get assertion request:"); + PrintAndLog("---------------- CBOR ------------------"); + TinyCborPrintFIDOPackage(fido2CmdGetAssertion, false, data, datalen); + PrintAndLog("---------------- CBOR ------------------"); + } + + res = FIDO2GetAssertion(data, datalen, buf, sizeof(buf), &len, &sw); + DropField(); + if (res) { + PrintAndLog("Can't execute get assertion command. res=%x. Exit...", res); + return res; + } + + if (sw != 0x9000) { + PrintAndLog("ERROR execute get assertion command. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + return 3; + } + + if(buf[0]) { + PrintAndLog("FIDO2 get assertion error: %d - %s", buf[0], fido2GetCmdErrorDescription(buf[0])); + return 0; + } + + PrintAndLog("GetAssertion result (%d b) OK.", len); + if (showCBOR) { + PrintAndLog("CBOR get assertion response:"); + PrintAndLog("---------------- CBOR ------------------"); + TinyCborPrintFIDOPackage(fido2CmdGetAssertion, true, &buf[1], len - 1); + PrintAndLog("---------------- CBOR ------------------"); + } + + // parse returned cbor + FIDO2GetAssertionParseRes(root, &buf[1], len - 1, verbose, verbose2, showCBOR); + + if (root) { + res = json_dump_file(root, fname, JSON_INDENT(2)); + if (res) { + PrintAndLog("ERROR: can't save the file: %s", fname); + return 200; + } + PrintAndLog("File `%s` saved.", fname); + } + + json_decref(root); + + return 0; +}; + static command_t CommandTable[] = { {"help", CmdHelp, 1, "This help."}, {"info", CmdHFFidoInfo, 0, "Info about FIDO tag."}, {"reg", CmdHFFidoRegister, 0, "FIDO U2F Registration Message."}, {"auth", CmdHFFidoAuthenticate, 0, "FIDO U2F Authentication Message."}, + {"make", CmdHFFido2MakeCredential, 0, "FIDO2 MakeCredential command."}, + {"assert", CmdHFFido2GetAssertion, 0, "FIDO2 GetAssertion command."}, {NULL, NULL, 0, NULL} }; diff --git a/client/fido/cbortools.c b/client/fido/cbortools.c new file mode 100644 index 00000000..01691dad --- /dev/null +++ b/client/fido/cbortools.c @@ -0,0 +1,491 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2018 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Tools for work with CBOR format http://cbor.io/spec.html +// via Intel tinycbor (https://github.com/intel/tinycbor) library +//----------------------------------------------------------------------------- +// + +#include "cbortools.h" +#include +#include "emv/emvjson.h" +#include "util.h" +#include "fidocore.h" + +static void indent(int nestingLevel) { + while (nestingLevel--) + printf(" "); +} + +static CborError dumpelm(CborValue *it, bool *got_next, int nestingLevel) { + CborError err; + *got_next = false; + + CborType type = cbor_value_get_type(it); + indent(nestingLevel); + switch (type) { + case CborMapType: + case CborArrayType: { + printf(type == CborArrayType ? "Array[" : "Map["); + break; + } + + case CborIntegerType: { + int64_t val; + cbor_value_get_int64(it, &val); // can't fail + printf("%lld", (long long)val); + break; + } + + case CborByteStringType: { + uint8_t *buf; + size_t n; + err = cbor_value_dup_byte_string(it, &buf, &n, it); + *got_next = true; + if (err) + return err; // parse error + printf("%s", sprint_hex(buf, n)); + free(buf); + break; + } + + case CborTextStringType: { + char *buf; + size_t n; + err = cbor_value_dup_text_string(it, &buf, &n, it); + *got_next = true; + if (err) + return err; // parse error + printf("%s", buf); + free(buf); + break; + } + + case CborTagType: { + CborTag tag; + cbor_value_get_tag(it, &tag); + printf("Tag(%lld)", (long long)tag); + break; + } + + case CborSimpleType: { + uint8_t type; + cbor_value_get_simple_type(it, &type); + printf("simple(%u)", type); + break; + } + + case CborNullType: + printf("null"); + break; + + case CborUndefinedType: + printf("undefined"); + break; + + case CborBooleanType: { + bool val; + cbor_value_get_boolean(it, &val); // can't fail + printf("%s", val ? "true" : "false"); + break; + } + + case CborDoubleType: { + double val; + if (false) { + float f; + case CborFloatType: + cbor_value_get_float(it, &f); + val = f; + } else { + cbor_value_get_double(it, &val); + } + printf("%g", val); + break; + } + case CborHalfFloatType: { + uint16_t val; + cbor_value_get_half_float(it, &val); + printf("__f16(%04x)", val); + break; + } + + case CborInvalidType: + printf("CborInvalidType!!!"); + break; + } + + return CborNoError; +} + +static CborError dumprecursive(uint8_t cmdCode, bool isResponse, CborValue *it, bool isMapType, int nestingLevel) { + int elmCount = 0; + while (!cbor_value_at_end(it)) { + CborError err; + CborType type = cbor_value_get_type(it); +//printf("^%x^", type); + bool got_next; + + switch (type) { + case CborMapType: + case CborArrayType: { + // recursive type + CborValue recursed; + assert(cbor_value_is_container(it)); + if (!(isMapType && (elmCount % 2))) + indent(nestingLevel); + printf(type == CborArrayType ? "Array[\n" : "Map[\n"); + err = cbor_value_enter_container(it, &recursed); + if (err) + return err; // parse error + err = dumprecursive(cmdCode, isResponse, &recursed, (type == CborMapType), nestingLevel + 1); + if (err) + return err; // parse error + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; // parse error + indent(nestingLevel); + printf("]"); + got_next = true; + break; + } + + default: { + err = dumpelm(it, &got_next, (isMapType && (elmCount % 2)) ? 0 : nestingLevel); + if (err) + return err; + if (cmdCode > 0 && nestingLevel == 1 && isMapType && !(elmCount % 2)) { + int64_t val; + cbor_value_get_int64(it, &val); + char *desc = fido2GetCmdMemberDescription(cmdCode, isResponse, val); + if (desc) + printf(" (%s)", desc); + } + break; + } + } + + if (!got_next) { + err = cbor_value_advance_fixed(it); + if (err) + return err; + } + if (isMapType && !(elmCount % 2)) { + printf(": "); + } else { + printf("\n"); + } + elmCount++; + } + return CborNoError; +} + +int TinyCborInit(uint8_t *data, size_t length, CborValue *cb) { + CborParser parser; + CborError err = cbor_parser_init(data, length, 0, &parser, cb); + if (err) + return err; + + return 0; +} + +int TinyCborPrintFIDOPackage(uint8_t cmdCode, bool isResponse, uint8_t *data, size_t length) { + CborValue cb; + int res; + res = TinyCborInit(data, length, &cb); + if (res) + return res; + + CborError err = dumprecursive(cmdCode, isResponse, &cb, false, 0); + + if (err) { + fprintf(stderr, +#if __WORDSIZE == 64 + "CBOR parsing failure at offset %" PRId64 " : %s\n", +#else + "CBOR parsing failure at offset %" PRId32 " : %s\n", +#endif + cb.ptr - data, cbor_error_string(err)); + return 1; + } + + return 0; +} + +int JsonObjElmCount(json_t *elm) { + int res = 0; + const char *key; + json_t *value; + + if (!json_is_object(elm)) + return 0; + + json_object_foreach(elm, key, value) { + if (strlen(key) > 0 && key[0] != '.') + res++; + } + + return res; +} + +int JsonToCbor(json_t *elm, CborEncoder *encoder) { + if (!elm || !encoder) + return 1; + + int res; + + // CBOR map == JSON object + if (json_is_object(elm)) { + CborEncoder map; + const char *key; + json_t *value; + + res = cbor_encoder_create_map(encoder, &map, JsonObjElmCount(elm)); + cbor_check(res); + + json_object_foreach(elm, key, value) { + if (strlen(key) > 0 && key[0] != '.') { + res = cbor_encode_text_stringz(&map, key); + cbor_check(res); + + // RECURSION! + JsonToCbor(value, &map); + } + } + + res = cbor_encoder_close_container(encoder, &map); + cbor_check(res); + } + + // CBOR array == JSON array + if (json_is_array(elm)) { + size_t index; + json_t *value; + CborEncoder array; + + res = cbor_encoder_create_array(encoder, &array, json_array_size(elm)); + cbor_check(res); + + json_array_foreach(elm, index, value) { + // RECURSION! + JsonToCbor(value, &array); + } + + res = cbor_encoder_close_container(encoder, &array); + cbor_check(res); + } + + if (json_is_boolean(elm)) { + res = cbor_encode_boolean(encoder, json_is_true(elm)); + cbor_check(res); + } + + if (json_is_integer(elm)) { + res = cbor_encode_int(encoder, json_integer_value(elm)); + cbor_check(res); + } + + if (json_is_real(elm)) { + res = cbor_encode_float(encoder, json_real_value(elm)); + cbor_check(res); + } + + if (json_is_string(elm)) { + const char * val = json_string_value(elm); + if (CheckStringIsHEXValue(val)) { + size_t datalen = 0; + uint8_t data[4096] = {0}; + res = JsonLoadBufAsHex(elm, "$", data, sizeof(data), &datalen); + if (res) + return 100; + + res = cbor_encode_byte_string(encoder, data, datalen); + cbor_check(res); + } else { + res = cbor_encode_text_stringz(encoder, val); + cbor_check(res); + } + } + + + + return 0; +} + +int CborMapGetKeyById(CborParser *parser, CborValue *map, uint8_t *data, size_t dataLen, int key) { + CborValue cb; + + CborError err = cbor_parser_init(data, dataLen, 0, parser, &cb); + cbor_check(err); + + if (cbor_value_get_type(&cb) != CborMapType) + return 1; + + err = cbor_value_enter_container(&cb, map); + cbor_check(err); + + int64_t indx; + while (!cbor_value_at_end(map)) { + // check number + if (cbor_value_get_type(map) != CborIntegerType) + return 1; + + cbor_value_get_int64(map, &indx); + + err = cbor_value_advance(map); + cbor_check(err); + + if (indx == key) + return 0; + + // pass value + err = cbor_value_advance(map); + cbor_check(err); + } + + err = cbor_value_leave_container(&cb, map); + cbor_check(err); + + return 2; +} + +CborError CborGetArrayBinStringValue(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen) { + return CborGetArrayBinStringValueEx(elm, data, maxdatalen, datalen, NULL, 0); +} + +CborError CborGetArrayBinStringValueEx(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen, uint8_t *delimeter, size_t delimeterlen) { + CborValue array; + if (datalen) + *datalen = 0; + + size_t slen = maxdatalen; + size_t totallen = 0; + + CborError res = cbor_value_enter_container(elm, &array); + cbor_check(res); + + while (!cbor_value_at_end(&array)) { + res = cbor_value_copy_byte_string(&array, &data[totallen], &slen, &array); + cbor_check(res); + + totallen += slen; + if (delimeter) { + memcpy(&data[totallen], delimeter, delimeterlen); + totallen += delimeterlen; + } + slen = maxdatalen - totallen; + } + + res = cbor_value_leave_container(elm, &array); + cbor_check(res); + + if (datalen) + *datalen = totallen; + + return CborNoError; +}; + +CborError CborGetBinStringValue(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen) { + if (datalen) + *datalen = 0; + + size_t slen = maxdatalen; + + CborError res = cbor_value_copy_byte_string(elm, data, &slen, elm); + cbor_check(res); + + if (datalen) + *datalen = slen; + + return CborNoError; +}; + +CborError CborGetArrayStringValue(CborValue *elm, char *data, size_t maxdatalen, size_t *datalen, char *delimeter) { + CborValue array; + if (datalen) + *datalen = 0; + + size_t slen = maxdatalen; + size_t totallen = 0; + + CborError res = cbor_value_enter_container(elm, &array); + cbor_check(res); + + while (!cbor_value_at_end(&array)) { + res = cbor_value_copy_text_string(&array, &data[totallen], &slen, &array); + cbor_check(res); + + totallen += slen; + if (delimeter) { + strcat(data, delimeter); + totallen += strlen(delimeter); + } + slen = maxdatalen - totallen; + data[totallen] = 0x00; + } + + res = cbor_value_leave_container(elm, &array); + cbor_check(res); + + if (datalen) + *datalen = totallen; + + return CborNoError; +}; + +CborError CborGetStringValue(CborValue *elm, char *data, size_t maxdatalen, size_t *datalen) { + if (datalen) + *datalen = 0; + + size_t slen = maxdatalen; + + CborError res = cbor_value_copy_text_string(elm, data, &slen, elm); + cbor_check(res); + + if (datalen) + *datalen = slen; + + return CborNoError; +}; + +CborError CborGetStringValueBuf(CborValue *elm) { + static char stringBuf[2048]; + memset(stringBuf, 0x00, sizeof(stringBuf)); + + return CborGetStringValue(elm, stringBuf, sizeof(stringBuf), NULL); +}; + +int CBOREncodeElm(json_t *root, char *rootElmId, CborEncoder *encoder) { + json_t *elm = NULL; + if (rootElmId && strlen(rootElmId) && rootElmId[0] == '$') + elm = json_path_get(root, rootElmId); + else + elm = json_object_get(root, rootElmId); + + if (!elm) + return 1; + + int res = JsonToCbor(elm, encoder); + + return res; +} + +CborError CBOREncodeClientDataHash(json_t *root, CborEncoder *encoder) { + uint8_t buf[100] = {0}; + size_t jlen; + + JsonLoadBufAsHex(root, "$.ClientDataHash", buf, sizeof(buf), &jlen); + + // fill with 0x00 if not found + if (!jlen) + jlen = 32; + + int res = cbor_encode_byte_string(encoder, buf, jlen); + cbor_check(res); + + return 0; +} diff --git a/client/fido/cbortools.h b/client/fido/cbortools.h new file mode 100644 index 00000000..1e3fa8c0 --- /dev/null +++ b/client/fido/cbortools.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2018 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Tools for work with CBOR format http://cbor.io/spec.html +// via Intel tinycbor (https://github.com/intel/tinycbor) library +//----------------------------------------------------------------------------- +// + +#ifndef __CBORTOOLS_H__ +#define __CBORTOOLS_H__ + +#include +#include +#include +#include + +#define cbor_check_if(r) if ((r) != CborNoError) {return r;} else +#define cbor_check(r) if ((r) != CborNoError) return r; + +extern int TinyCborPrintFIDOPackage(uint8_t cmdCode, bool isResponse, uint8_t *data, size_t length); +extern int JsonToCbor(json_t *elm, CborEncoder *encoder); + +extern int CborMapGetKeyById(CborParser *parser, CborValue *map, uint8_t *data, size_t dataLen, int key); +extern CborError CborGetArrayBinStringValue(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen); +extern CborError CborGetArrayBinStringValueEx(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen, uint8_t *delimeter, size_t delimeterlen); +extern CborError CborGetBinStringValue(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen); +extern CborError CborGetArrayStringValue(CborValue *elm, char *data, size_t maxdatalen, size_t *datalen, char *delimeter); +extern CborError CborGetStringValue(CborValue *elm, char *data, size_t maxdatalen, size_t *datalen); +extern CborError CborGetStringValueBuf(CborValue *elm); + +extern int CBOREncodeElm(json_t *root, char *rootElmId, CborEncoder *encoder); +extern CborError CBOREncodeClientDataHash(json_t *root, CborEncoder *encoder); + +#endif /* __CBORTOOLS_H__ */ diff --git a/client/fido/cose.c b/client/fido/cose.c new file mode 100644 index 00000000..1184250f --- /dev/null +++ b/client/fido/cose.c @@ -0,0 +1,240 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2018 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Tools for work with COSE (CBOR Object Signing and Encryption) rfc8152 +// https://tools.ietf.org/html/rfc8152 +//----------------------------------------------------------------------------- +// + +#include "cose.h" +#include +#include "cbortools.h" +#include "util.h" +#include "ui.h" + +static const char COSEEmptyStr[] = ""; + +typedef struct { + int Value; + char *Name; + char *Description; +} COSEValueNameDesc_t; + +typedef struct { + int Value; + char *Type; + char *Name; + char *Description; +} COSEValueTypeNameDesc_t; + +// kty - Key Type Values +COSEValueNameDesc_t COSEKeyTypeValueDesc[] = { + {0, "Reserved", "Reserved"}, + {1, "OKP", "Octet Key Pair"}, + {2, "EC2", "Elliptic Curve Key w/ x- and y-coordinate pair"}, + {4, "Symmetric", "Symmetric Key"}, +}; + +COSEValueNameDesc_t *GetCOSEktyElm(int id) { + for (int i = 0; i < ARRAYLEN(COSEKeyTypeValueDesc); i++) + if (COSEKeyTypeValueDesc[i].Value == id) + return &COSEKeyTypeValueDesc[i]; + return NULL; +} + +const char *GetCOSEktyDescription(int id) { + COSEValueNameDesc_t *elm = GetCOSEktyElm(id); + if (elm) + return elm->Description; + return COSEEmptyStr; +} + +// keys +COSEValueTypeNameDesc_t COSECurvesDesc[] = { + {1, "EC2", "P-256", "NIST P-256 also known as secp256r1"}, + {2, "EC2", "P-384", "NIST P-384 also known as secp384r1"}, + {3, "EC2", "P-521", "NIST P-521 also known as secp521r1"}, + {4, "OKP", "X25519", "X25519 for use w/ ECDH only"}, + {5, "OKP", "X448", "X448 for use w/ ECDH only"}, + {6, "OKP", "Ed25519", "Ed25519 for use w/ EdDSA only"}, + {7, "OKP", "Ed448", "Ed448 for use w/ EdDSA only"}, +}; + +COSEValueTypeNameDesc_t *GetCOSECurveElm(int id) { + for (int i = 0; i < ARRAYLEN(COSECurvesDesc); i++) + if (COSECurvesDesc[i].Value == id) + return &COSECurvesDesc[i]; + return NULL; +} + +const char *GetCOSECurveDescription(int id) { + COSEValueTypeNameDesc_t *elm = GetCOSECurveElm(id); + if (elm) + return elm->Description; + return COSEEmptyStr; +} + +// RFC8152 https://www.iana.org/assignments/cose/cose.xhtml#algorithms +COSEValueNameDesc_t COSEAlg[] = { + {-65536, "Unassigned", "Unassigned"}, + {-65535, "RS1", "RSASSA-PKCS1-v1_5 w/ SHA-1"}, + {-259, "RS512", "RSASSA-PKCS1-v1_5 w/ SHA-512"}, + {-258, "RS384", "RSASSA-PKCS1-v1_5 w/ SHA-384"}, + {-257, "RS256", "RSASSA-PKCS1-v1_5 w/ SHA-256"}, + {-42, "RSAES-OAEP w/ SHA-512", "RSAES-OAEP w/ SHA-512"}, + {-41, "RSAES-OAEP w/ SHA-256", "RSAES-OAEP w/ SHA-256"}, + {-40, "RSAES-OAEP w/ RFC 8017 def param", "RSAES-OAEP w/ SHA-1"}, + {-39, "PS512", "RSASSA-PSS w/ SHA-512"}, + {-38, "PS384", "RSASSA-PSS w/ SHA-384"}, + {-37, "PS256", "RSASSA-PSS w/ SHA-256"}, + {-36, "ES512", "ECDSA w/ SHA-512"}, + {-35, "ES384", "ECDSA w/ SHA-384"}, + {-34, "ECDH-SS + A256KW", "ECDH SS w/ Concat KDF and AES Key Wrap w/ 256-bit key"}, + {-33, "ECDH-SS + A192KW", "ECDH SS w/ Concat KDF and AES Key Wrap w/ 192-bit key"}, + {-32, "ECDH-SS + A128KW", "ECDH SS w/ Concat KDF and AES Key Wrap w/ 128-bit key"}, + {-31, "ECDH-ES + A256KW", "ECDH ES w/ Concat KDF and AES Key Wrap w/ 256-bit key"}, + {-30, "ECDH-ES + A192KW", "ECDH ES w/ Concat KDF and AES Key Wrap w/ 192-bit key"}, + {-29, "ECDH-ES + A128KW", "ECDH ES w/ Concat KDF and AES Key Wrap w/ 128-bit key"}, + {-28, "ECDH-SS + HKDF-512", "ECDH SS w/ HKDF - generate key directly"}, + {-27, "ECDH-SS + HKDF-256", "ECDH SS w/ HKDF - generate key directly"}, + {-26, "ECDH-ES + HKDF-512", "ECDH ES w/ HKDF - generate key directly"}, + {-25, "ECDH-ES + HKDF-256", "ECDH ES w/ HKDF - generate key directly"}, + {-13, "direct+HKDF-AES-256", "Shared secret w/ AES-MAC 256-bit key"}, + {-12, "direct+HKDF-AES-128", "Shared secret w/ AES-MAC 128-bit key"}, + {-11, "direct+HKDF-SHA-512", "Shared secret w/ HKDF and SHA-512"}, + {-10, "direct+HKDF-SHA-256", "Shared secret w/ HKDF and SHA-256"}, + {-8, "EdDSA", "EdDSA"}, + {-7, "ES256", "ECDSA w/ SHA-256"}, + {-6, "direct", "Direct use of CEK"}, + {-5, "A256KW", "AES Key Wrap w/ 256-bit key"}, + {-4, "A192KW", "AES Key Wrap w/ 192-bit key"}, + {-3, "A128KW", "AES Key Wrap w/ 128-bit key"}, + {0, "Reserved", "Reserved"}, + {1, "A128GCM", "AES-GCM mode w/ 128-bit key, 128-bit tag"}, + {2, "A192GCM", "AES-GCM mode w/ 192-bit key, 128-bit tag"}, + {3, "A256GCM", "AES-GCM mode w/ 256-bit key, 128-bit tag"}, + {4, "HMAC 256/64", "HMAC w/ SHA-256 truncated to 64 bits"}, + {5, "HMAC 256/256", "HMAC w/ SHA-256"}, + {6, "HMAC 384/384", "HMAC w/ SHA-384"}, + {7, "HMAC 512/512", "HMAC w/ SHA-512"}, + {10, "AES-CCM-16-64-128", "AES-CCM mode 128-bit key, 64-bit tag, 13-byte nonce"}, + {11, "AES-CCM-16-64-256", "AES-CCM mode 256-bit key, 64-bit tag, 13-byte nonce"}, + {12, "AES-CCM-64-64-128", "AES-CCM mode 128-bit key, 64-bit tag, 7-byte nonce"}, + {13, "AES-CCM-64-64-256", "AES-CCM mode 256-bit key, 64-bit tag, 7-byte nonce"}, + {14, "AES-MAC 128/64", "AES-MAC 128-bit key, 64-bit tag"}, + {15, "AES-MAC 256/64", "AES-MAC 256-bit key, 64-bit tag"}, + {24, "ChaCha20/Poly1305", "ChaCha20/Poly1305 w/ 256-bit key, 128-bit tag"}, + {25, "AES-MAC 128/128", "AES-MAC 128-bit key, 128-bit tag"}, + {26, "AES-MAC 256/128", "AES-MAC 256-bit key, 128-bit tag"}, + {30, "AES-CCM-16-128-128", "AES-CCM mode 128-bit key, 128-bit tag, 13-byte nonce"}, + {31, "AES-CCM-16-128-256", "AES-CCM mode 256-bit key, 128-bit tag, 13-byte nonce"}, + {32, "AES-CCM-64-128-128", "AES-CCM mode 128-bit key, 128-bit tag, 7-byte nonce"}, + {33, "AES-CCM-64-128-256", "AES-CCM mode 256-bit key, 128-bit tag, 7-byte nonce"} +}; + +COSEValueNameDesc_t *GetCOSEAlgElm(int id) { + for (int i = 0; i < ARRAYLEN(COSEAlg); i++) + if (COSEAlg[i].Value == id) + return &COSEAlg[i]; + return NULL; +} + +const char *GetCOSEAlgName(int id) { + COSEValueNameDesc_t *elm = GetCOSEAlgElm(id); + if (elm) + return elm->Name; + return COSEEmptyStr; +} + +const char *GetCOSEAlgDescription(int id) { + COSEValueNameDesc_t *elm = GetCOSEAlgElm(id); + if (elm) + return elm->Description; + return COSEEmptyStr; +} + +int COSEGetECDSAKey(uint8_t *data, size_t datalen, bool verbose, uint8_t *public_key) { + CborParser parser; + CborValue map; + int64_t i64; + size_t len; + + if(verbose) + PrintAndLog("----------- CBOR decode ----------------"); + + // kty + int res = CborMapGetKeyById(&parser, &map, data, datalen, 1); + if(!res) { + cbor_value_get_int64(&map, &i64); + if(verbose) + PrintAndLog("kty [%lld] %s", (long long)i64, GetCOSEktyDescription(i64)); + if (i64 != 2) + PrintAndLog("ERROR: kty must be 2."); + } + + // algorithm + res = CborMapGetKeyById(&parser, &map, data, datalen, 3); + if(!res) { + cbor_value_get_int64(&map, &i64); + if(verbose) + PrintAndLog("algorithm [%lld] %s", (long long)i64, GetCOSEAlgDescription(i64)); + if (i64 != -7) + PrintAndLog("ERROR: algorithm must be -7."); + } + + // curve + res = CborMapGetKeyById(&parser, &map, data, datalen, -1); + if(!res) { + cbor_value_get_int64(&map, &i64); + if(verbose) + PrintAndLog("curve [%lld] %s", (long long)i64, GetCOSECurveDescription(i64)); + if (i64 != 1) + PrintAndLog("ERROR: curve must be 1."); + } + + // plain key + public_key[0] = 0x04; + + // x - coordinate + res = CborMapGetKeyById(&parser, &map, data, datalen, -2); + if(!res) { + res = CborGetBinStringValue(&map, &public_key[1], 32, &len); + cbor_check(res); + if(verbose) + PrintAndLog("x - coordinate [%d]: %s", len, sprint_hex(&public_key[1], 32)); + if (len != 32) + PrintAndLog("ERROR: x - coordinate length must be 32."); + } + + // y - coordinate + res = CborMapGetKeyById(&parser, &map, data, datalen, -3); + if(!res) { + res = CborGetBinStringValue(&map, &public_key[33], 32, &len); + cbor_check(res); + if(verbose) + PrintAndLog("y - coordinate [%d]: %s", len, sprint_hex(&public_key[33], 32)); + if (len != 32) + PrintAndLog("ERROR: y - coordinate length must be 32."); + } + + // d - private key + uint8_t private_key[128] = {0}; + res = CborMapGetKeyById(&parser, &map, data, datalen, -4); + if(!res) { + res = CborGetBinStringValue(&map, private_key, sizeof(private_key), &len); + cbor_check(res); + if(verbose) + PrintAndLog("d - private key [%d]: %s", len, sprint_hex(private_key, len)); + } + + if(verbose) + PrintAndLog("----------- CBOR decode ----------------"); + + return 0; +} + + diff --git a/client/fido/cose.h b/client/fido/cose.h new file mode 100644 index 00000000..850652fc --- /dev/null +++ b/client/fido/cose.h @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2018 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Tools for work with COSE (CBOR Object Signing and Encryption) rfc8152 +// https://tools.ietf.org/html/rfc8152 +//----------------------------------------------------------------------------- +// + +#ifndef __COSE_H__ +#define __COSE_H__ + +#include +#include +#include + +extern const char *GetCOSEAlgName(int id); +extern const char *GetCOSEAlgDescription(int id); +extern const char *GetCOSEktyDescription(int id); +extern const char *GetCOSECurveDescription(int id); + +extern int COSEGetECDSAKey(uint8_t *data, size_t datalen, bool verbose, uint8_t *public_key); + +#endif /* __COSE_H__ */ diff --git a/client/fido/fido2.json b/client/fido/fido2.json new file mode 100644 index 00000000..abbfae5d --- /dev/null +++ b/client/fido/fido2.json @@ -0,0 +1,33 @@ +{ + "ClientDataHash": "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", + "RelyingPartyEntity": { + "id": "acme.com", + "name": "Acme" + }, + "UserEntity": { + "id": "00000000000000000000000000000001", + "icon": "https://pics.acme.com/00/p/aBjjjpqPb.png", + "name": "johnpsmith@acme.com", + "displayName": "John P. Smith" + }, + "pubKeyCredParams": [ + { + "type": "public-key", + "alg": -7, + ".name": "ES256" + }, + { + "type": "public-key", + "alg": -257, + ".name": "RS256" + } + ], + "MakeCredentialOptions": { + "uv": false, + "rk": true + }, + "GetAssertionOptions": { + "up": true, + "uv": false + } +} diff --git a/client/fido/fidocore.c b/client/fido/fidocore.c new file mode 100644 index 00000000..39c2052f --- /dev/null +++ b/client/fido/fidocore.c @@ -0,0 +1,805 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2018 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// FIDO2 authenticators core data and commands +// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html +//----------------------------------------------------------------------------- +// + +#include "fidocore.h" +#include "emv/emvcore.h" +#include "emv/emvjson.h" +#include +#include "cbortools.h" +#include +#include +#include +#include "crypto/asn1utils.h" +#include "crypto/libpcrypto.h" +#include "fido/additional_ca.h" +#include "fido/cose.h" + +typedef struct { + uint8_t ErrorCode; + char *ShortDescription; + char *Description; +} fido2Error_t; + +fido2Error_t fido2Errors[] = { + {0xFF, "n/a", "n/a"}, + {0x00, "CTAP1_ERR_SUCCESS", "Indicates successful response."}, + {0x01, "CTAP1_ERR_INVALID_COMMAND", "The command is not a valid CTAP command."}, + {0x02, "CTAP1_ERR_INVALID_PARAMETER", "The command included an invalid parameter."}, + {0x03, "CTAP1_ERR_INVALID_LENGTH", "Invalid message or item length."}, + {0x04, "CTAP1_ERR_INVALID_SEQ", "Invalid message sequencing."}, + {0x05, "CTAP1_ERR_TIMEOUT", "Message timed out."}, + {0x06, "CTAP1_ERR_CHANNEL_BUSY", "Channel busy."}, + {0x0A, "CTAP1_ERR_LOCK_REQUIRED", "Command requires channel lock."}, + {0x0B, "CTAP1_ERR_INVALID_CHANNEL", "Command not allowed on this cid."}, + {0x10, "CTAP2_ERR_CBOR_PARSING", "Error while parsing CBOR."}, + {0x11, "CTAP2_ERR_CBOR_UNEXPECTED_TYPE", "Invalid/unexpected CBOR error."}, + {0x12, "CTAP2_ERR_INVALID_CBOR", "Error when parsing CBOR."}, + {0x13, "CTAP2_ERR_INVALID_CBOR_TYPE", "Invalid or unexpected CBOR type."}, + {0x14, "CTAP2_ERR_MISSING_PARAMETER", "Missing non-optional parameter."}, + {0x15, "CTAP2_ERR_LIMIT_EXCEEDED", "Limit for number of items exceeded."}, + {0x16, "CTAP2_ERR_UNSUPPORTED_EXTENSION", "Unsupported extension."}, + {0x17, "CTAP2_ERR_TOO_MANY_ELEMENTS", "Limit for number of items exceeded."}, + {0x18, "CTAP2_ERR_EXTENSION_NOT_SUPPORTED", "Unsupported extension."}, + {0x19, "CTAP2_ERR_CREDENTIAL_EXCLUDED", "Valid credential found in the exludeList."}, + {0x20, "CTAP2_ERR_CREDENTIAL_NOT_VALID", "Credential not valid for authenticator."}, + {0x21, "CTAP2_ERR_PROCESSING", "Processing (Lengthy operation is in progress)."}, + {0x22, "CTAP2_ERR_INVALID_CREDENTIAL", "Credential not valid for the authenticator."}, + {0x23, "CTAP2_ERR_USER_ACTION_PENDING", "Authentication is waiting for user interaction."}, + {0x24, "CTAP2_ERR_OPERATION_PENDING", "Processing, lengthy operation is in progress."}, + {0x25, "CTAP2_ERR_NO_OPERATIONS", "No request is pending."}, + {0x26, "CTAP2_ERR_UNSUPPORTED_ALGORITHM", "Authenticator does not support requested algorithm."}, + {0x27, "CTAP2_ERR_OPERATION_DENIED", "Not authorized for requested operation."}, + {0x28, "CTAP2_ERR_KEY_STORE_FULL", "Internal key storage is full."}, + {0x29, "CTAP2_ERR_NOT_BUSY", "Authenticator cannot cancel as it is not busy."}, + {0x2A, "CTAP2_ERR_NO_OPERATION_PENDING", "No outstanding operations."}, + {0x2B, "CTAP2_ERR_UNSUPPORTED_OPTION", "Unsupported option."}, + {0x2C, "CTAP2_ERR_INVALID_OPTION", "Unsupported option."}, + {0x2D, "CTAP2_ERR_KEEPALIVE_CANCEL", "Pending keep alive was cancelled."}, + {0x2E, "CTAP2_ERR_NO_CREDENTIALS", "No valid credentials provided."}, + {0x2F, "CTAP2_ERR_USER_ACTION_TIMEOUT", "Timeout waiting for user interaction."}, + {0x30, "CTAP2_ERR_NOT_ALLOWED", "Continuation command, such as, authenticatorGetNextAssertion not allowed."}, + {0x31, "CTAP2_ERR_PIN_INVALID", "PIN Blocked."}, + {0x32, "CTAP2_ERR_PIN_BLOCKED", "PIN Blocked."}, + {0x33, "CTAP2_ERR_PIN_AUTH_INVALID", "PIN authentication,pinAuth, verification failed."}, + {0x34, "CTAP2_ERR_PIN_AUTH_BLOCKED", "PIN authentication,pinAuth, blocked. Requires power recycle to reset."}, + {0x35, "CTAP2_ERR_PIN_NOT_SET", "No PIN has been set."}, + {0x36, "CTAP2_ERR_PIN_REQUIRED", "PIN is required for the selected operation."}, + {0x37, "CTAP2_ERR_PIN_POLICY_VIOLATION", "PIN policy violation. Currently only enforces minimum length."}, + {0x38, "CTAP2_ERR_PIN_TOKEN_EXPIRED", "pinToken expired on authenticator."}, + {0x39, "CTAP2_ERR_REQUEST_TOO_LARGE", "Authenticator cannot handle this request due to memory constraints."}, + {0x7F, "CTAP1_ERR_OTHER", "Other unspecified error."}, + {0xDF, "CTAP2_ERR_SPEC_LAST", "CTAP 2 spec last error."}, +}; + +typedef struct { + fido2Commands Command; + fido2PacketType PckType; + int MemberNumber; + char *Description; +} fido2Desc_t; + +fido2Desc_t fido2CmdGetInfoRespDesc[] = { + {fido2CmdMakeCredential, ptResponse, 0x01, "fmt"}, + {fido2CmdMakeCredential, ptResponse, 0x02, "authData"}, + {fido2CmdMakeCredential, ptResponse, 0x03, "attStmt"}, + + {fido2CmdMakeCredential, ptQuery, 0x01, "clientDataHash"}, + {fido2CmdMakeCredential, ptQuery, 0x02, "rp"}, + {fido2CmdMakeCredential, ptQuery, 0x03, "user"}, + {fido2CmdMakeCredential, ptQuery, 0x04, "pubKeyCredParams"}, + {fido2CmdMakeCredential, ptQuery, 0x05, "excludeList"}, + {fido2CmdMakeCredential, ptQuery, 0x06, "extensions"}, + {fido2CmdMakeCredential, ptQuery, 0x07, "options"}, + {fido2CmdMakeCredential, ptQuery, 0x08, "pinAuth"}, + {fido2CmdMakeCredential, ptQuery, 0x09, "pinProtocol"}, + + {fido2CmdGetAssertion, ptResponse, 0x01, "credential"}, + {fido2CmdGetAssertion, ptResponse, 0x02, "authData"}, + {fido2CmdGetAssertion, ptResponse, 0x03, "signature"}, + {fido2CmdGetAssertion, ptResponse, 0x04, "publicKeyCredentialUserEntity"}, + {fido2CmdGetAssertion, ptResponse, 0x05, "numberOfCredentials"}, + + {fido2CmdGetAssertion, ptQuery, 0x01, "rpId"}, + {fido2CmdGetAssertion, ptQuery, 0x02, "clientDataHash"}, + {fido2CmdGetAssertion, ptQuery, 0x03, "allowList"}, + {fido2CmdGetAssertion, ptQuery, 0x04, "extensions"}, + {fido2CmdGetAssertion, ptQuery, 0x05, "options"}, + {fido2CmdGetAssertion, ptQuery, 0x06, "pinAuth"}, + {fido2CmdGetAssertion, ptQuery, 0x07, "pinProtocol"}, + + {fido2CmdGetNextAssertion, ptResponse, 0x01, "credential"}, + {fido2CmdGetNextAssertion, ptResponse, 0x02, "authData"}, + {fido2CmdGetNextAssertion, ptResponse, 0x03, "signature"}, + {fido2CmdGetNextAssertion, ptResponse, 0x04, "publicKeyCredentialUserEntity"}, + + {fido2CmdGetInfo, ptResponse, 0x01, "versions"}, + {fido2CmdGetInfo, ptResponse, 0x02, "extensions"}, + {fido2CmdGetInfo, ptResponse, 0x03, "aaguid"}, + {fido2CmdGetInfo, ptResponse, 0x04, "options"}, + {fido2CmdGetInfo, ptResponse, 0x05, "maxMsgSize"}, + {fido2CmdGetInfo, ptResponse, 0x06, "pinProtocols"}, + + {fido2CmdClientPIN, ptResponse, 0x01, "keyAgreement"}, + {fido2CmdClientPIN, ptResponse, 0x02, "pinToken"}, + {fido2CmdClientPIN, ptResponse, 0x03, "retries"}, + + {fido2CmdClientPIN, ptQuery, 0x01, "pinProtocol"}, + {fido2CmdClientPIN, ptQuery, 0x02, "subCommand"}, + {fido2CmdClientPIN, ptQuery, 0x03, "keyAgreement"}, + {fido2CmdClientPIN, ptQuery, 0x04, "pinAuth"}, + {fido2CmdClientPIN, ptQuery, 0x05, "newPinEnc"}, + {fido2CmdClientPIN, ptQuery, 0x06, "pinHashEnc"}, + {fido2CmdClientPIN, ptQuery, 0x07, "getKeyAgreement"}, + {fido2CmdClientPIN, ptQuery, 0x08, "getRetries"}, + + {fido2COSEKey, ptResponse, 0x01, "kty"}, + {fido2COSEKey, ptResponse, 0x03, "alg"}, + {fido2COSEKey, ptResponse, -1, "crv"}, + {fido2COSEKey, ptResponse, -2, "x - coordinate"}, + {fido2COSEKey, ptResponse, -3, "y - coordinate"}, + {fido2COSEKey, ptResponse, -4, "d - private key"}, +}; + +char *fido2GetCmdErrorDescription(uint8_t errorCode) { + for (int i = 0; i < sizeof(fido2Errors) / sizeof(fido2Error_t); i++) + if (fido2Errors[i].ErrorCode == errorCode) + return fido2Errors[i].Description; + + return fido2Errors[0].Description; +} + +char *fido2GetCmdMemberDescription(uint8_t cmdCode, bool isResponse, int memberNum) { + for (int i = 0; i < sizeof(fido2CmdGetInfoRespDesc) / sizeof(fido2Desc_t); i++) + if (fido2CmdGetInfoRespDesc[i].Command == cmdCode && + fido2CmdGetInfoRespDesc[i].PckType == (isResponse ? ptResponse : ptQuery) && + fido2CmdGetInfoRespDesc[i].MemberNumber == memberNum ) + return fido2CmdGetInfoRespDesc[i].Description; + + return NULL; +} + +int FIDOSelect(bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t data[] = {0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01}; + + return EMVSelect(ActivateField, LeaveFieldON, data, sizeof(data), Result, MaxResultLen, ResultLen, sw, NULL); +} + +int FIDOExchange(sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + int res = EMVExchange(true, apdu, Result, MaxResultLen, ResultLen, sw, NULL); + if (res == 5) // apdu result (sw) not a 0x9000 + res = 0; + // software chaining + while (!res && (*sw >> 8) == 0x61) { + size_t oldlen = *ResultLen; + res = EMVExchange(true, (sAPDU){0x00, 0xC0, 0x00, 0x00, 0x00, NULL}, &Result[oldlen], MaxResultLen - oldlen, ResultLen, sw, NULL); + if (res == 5) // apdu result (sw) not a 0x9000 + res = 0; + + *ResultLen += oldlen; + if (*ResultLen > MaxResultLen) + return 100; + } + return res; +} + +int FIDORegister(uint8_t *params, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return FIDOExchange((sAPDU){0x00, 0x01, 0x03, 0x00, 64, params}, Result, MaxResultLen, ResultLen, sw); +} + +int FIDOAuthentication(uint8_t *params, uint8_t paramslen, uint8_t controlb, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return FIDOExchange((sAPDU){0x00, 0x02, controlb, 0x00, paramslen, params}, Result, MaxResultLen, ResultLen, sw); +} + +int FIDO2GetInfo(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t data[] = {fido2CmdGetInfo}; + return FIDOExchange((sAPDU){0x80, 0x10, 0x00, 0x00, sizeof(data), data}, Result, MaxResultLen, ResultLen, sw); +} + +int FIDO2MakeCredential(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t data[paramslen + 1]; + data[0] = fido2CmdMakeCredential; + memcpy(&data[1], params, paramslen); + return FIDOExchange((sAPDU){0x80, 0x10, 0x00, 0x00, sizeof(data), data}, Result, MaxResultLen, ResultLen, sw); +} + +int FIDO2GetAssertion(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t data[paramslen + 1]; + data[0] = fido2CmdGetAssertion; + memcpy(&data[1], params, paramslen); + return FIDOExchange((sAPDU){0x80, 0x10, 0x00, 0x00, sizeof(data), data}, Result, MaxResultLen, ResultLen, sw); +} + +int FIDOCheckDERAndGetKey(uint8_t *der, size_t derLen, bool verbose, uint8_t *publicKey, size_t publicKeyMaxLen) { + int res; + + // load CA's + mbedtls_x509_crt cacert; + mbedtls_x509_crt_init(&cacert); + res = mbedtls_x509_crt_parse(&cacert, (const unsigned char *) additional_ca_pem, additional_ca_pem_len); + if (res < 0) { + PrintAndLog("ERROR: CA parse certificate returned -0x%x - %s", -res, ecdsa_get_error(res)); + } + if (verbose) + PrintAndLog("CA load OK. %d skipped", res); + + // load DER certificate from authenticator's data + mbedtls_x509_crt cert; + mbedtls_x509_crt_init(&cert); + res = mbedtls_x509_crt_parse_der(&cert, der, derLen); + if (res) { + PrintAndLog("ERROR: DER parse returned 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res)); + } + + // get certificate info + char linfo[300] = {0}; + if (verbose) { + mbedtls_x509_crt_info(linfo, sizeof(linfo), " ", &cert); + PrintAndLog("DER certificate info:\n%s", linfo); + } + + // verify certificate + uint32_t verifyflags = 0; + res = mbedtls_x509_crt_verify(&cert, &cacert, NULL, NULL, &verifyflags, NULL, NULL); + if (res) { + PrintAndLog("ERROR: DER verify returned 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res)); + } else { + PrintAndLog("Certificate OK."); + } + + if (verbose) { + memset(linfo, 0x00, sizeof(linfo)); + mbedtls_x509_crt_verify_info(linfo, sizeof(linfo), " ", verifyflags); + PrintAndLog("Verification info:\n%s", linfo); + } + + // get public key + res = ecdsa_public_key_from_pk(&cert.pk, publicKey, publicKeyMaxLen); + if (res) { + PrintAndLog("ERROR: getting public key from certificate 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res)); + } else { + if (verbose) + PrintAndLog("Got a public key from certificate:\n%s", sprint_hex_inrow(publicKey, 65)); + } + + if (verbose) + PrintAndLog("------------------DER-------------------"); + + mbedtls_x509_crt_free(&cert); + mbedtls_x509_crt_free(&cacert); + + return 0; +} + +#define fido_check_if(r) if ((r) != CborNoError) {return r;} else +#define fido_check(r) if ((r) != CborNoError) return r; + +int FIDO2CreateMakeCredentionalReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen) { + if (datalen) + *datalen = 0; + if (!root || !data || !maxdatalen) + return 1; + + int res; + CborEncoder encoder; + CborEncoder map; + + cbor_encoder_init(&encoder, data, maxdatalen, 0); + + // create main map + res = cbor_encoder_create_map(&encoder, &map, 5); + fido_check_if(res) { + // clientDataHash + res = cbor_encode_uint(&map, 1); + fido_check_if(res) { + res = CBOREncodeClientDataHash(root, &map); + fido_check(res); + } + + // rp + res = cbor_encode_uint(&map, 2); + fido_check_if(res) { + res = CBOREncodeElm(root, "RelyingPartyEntity", &map); + fido_check(res); + } + + // user + res = cbor_encode_uint(&map, 3); + fido_check_if(res) { + res = CBOREncodeElm(root, "UserEntity", &map); + fido_check(res); + } + + // pubKeyCredParams + res = cbor_encode_uint(&map, 4); + fido_check_if(res) { + res = CBOREncodeElm(root, "pubKeyCredParams", &map); + fido_check(res); + } + + // options + res = cbor_encode_uint(&map, 7); + fido_check_if(res) { + res = CBOREncodeElm(root, "MakeCredentialOptions", &map); + fido_check(res); + } + } + res = cbor_encoder_close_container(&encoder, &map); + fido_check(res); + + size_t len = cbor_encoder_get_buffer_size(&encoder, data); + if (datalen) + *datalen = len; + + return 0; +} + +bool CheckrpIdHash(json_t *json, uint8_t *hash) { + char hashval[300] = {0}; + uint8_t hash2[32] = {0}; + + JsonLoadStr(json, "$.RelyingPartyEntity.id", hashval); + sha256hash((uint8_t *)hashval, strlen(hashval), hash2); + + return !memcmp(hash, hash2, 32); +} + +// check ANSI X9.62 format ECDSA signature (on P-256) +int FIDO2CheckSignature(json_t *root, uint8_t *publickey, uint8_t *sign, size_t signLen, uint8_t *authData, size_t authDataLen, bool verbose) { + int res; + uint8_t rval[300] = {0}; + uint8_t sval[300] = {0}; + res = ecdsa_asn1_get_signature(sign, signLen, rval, sval); + if (!res) { + if (verbose) { + PrintAndLog(" r: %s", sprint_hex(rval, 32)); + PrintAndLog(" s: %s", sprint_hex(sval, 32)); + } + + uint8_t clientDataHash[32] = {0}; + size_t clientDataHashLen = 0; + res = JsonLoadBufAsHex(root, "$.ClientDataHash", clientDataHash, sizeof(clientDataHash), &clientDataHashLen); + if (res || clientDataHashLen != 32) { + PrintAndLog("ERROR: Can't get clientDataHash from json!"); + return 2; + } + + uint8_t xbuf[4096] = {0}; + size_t xbuflen = 0; + res = FillBuffer(xbuf, sizeof(xbuf), &xbuflen, + authData, authDataLen, // rpIdHash[32] + flags[1] + signCount[4] + clientDataHash, 32, // Hash of the serialized client data. "$.ClientDataHash" from json + NULL, 0); + //PrintAndLog("--xbuf(%d)[%d]: %s", res, xbuflen, sprint_hex(xbuf, xbuflen)); + res = ecdsa_signature_verify(publickey, xbuf, xbuflen, sign, signLen); + if (res) { + if (res == -0x4e00) { + PrintAndLog("Signature is NOT VALID."); + } else { + PrintAndLog("Other signature check error: %x %s", (res<0)?-res:res, ecdsa_get_error(res)); + } + return res; + } else { + PrintAndLog("Signature is OK."); + } + } else { + PrintAndLog("Invalid signature. res=%d.", res); + return res; + } + + return 0; +} + +int FIDO2MakeCredentionalParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR, bool showDERTLV) { + CborParser parser; + CborValue map, mapsmt; + int res; + char *buf; + uint8_t *ubuf; + size_t n; + + // fmt + res = CborMapGetKeyById(&parser, &map, data, dataLen, 1); + if (res) + return res; + + res = cbor_value_dup_text_string(&map, &buf, &n, &map); + cbor_check(res); + PrintAndLog("format: %s", buf); + free(buf); + + // authData + uint8_t authData[400] = {0}; + size_t authDataLen = 0; + res = CborMapGetKeyById(&parser, &map, data, dataLen, 2); + if (res) + return res; + res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map); + cbor_check(res); + + authDataLen = n; + memcpy(authData, ubuf, authDataLen); + + if (verbose2) { + PrintAndLog("authData[%d]: %s", n, sprint_hex_inrow(authData, authDataLen)); + } else { + PrintAndLog("authData[%d]: %s...", n, sprint_hex(authData, MIN(authDataLen, 16))); + } + + PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf, 32)); + + // check RP ID Hash + if (CheckrpIdHash(root, ubuf)) { + PrintAndLog("rpIdHash OK."); + } else { + PrintAndLog("rpIdHash ERROR!"); + } + + PrintAndLog("Flags 0x%02x:", ubuf[32]); + if (!ubuf[32]) + PrintAndLog("none"); + if (ubuf[32] & 0x01) + PrintAndLog("up - user presence result"); + if (ubuf[32] & 0x04) + PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result"); + if (ubuf[32] & 0x40) + PrintAndLog("at - attested credential data included"); + if (ubuf[32] & 0x80) + PrintAndLog("ed - extension data included"); + + uint32_t cntr = (uint32_t)bytes_to_num(&ubuf[33], 4); + PrintAndLog("Counter: %d", cntr); + JsonSaveInt(root, "$.AppData.Counter", cntr); + + // attestation data + PrintAndLog("AAGUID: %s", sprint_hex(&ubuf[37], 16)); + JsonSaveBufAsHexCompact(root, "$.AppData.AAGUID", &ubuf[37], 16); + + // Credential ID + uint8_t cridlen = (uint16_t)bytes_to_num(&ubuf[53], 2); + PrintAndLog("Credential id[%d]: %s", cridlen, sprint_hex_inrow(&ubuf[55], cridlen)); + JsonSaveInt(root, "$.AppData.CredentialIdLen", cridlen); + JsonSaveBufAsHexCompact(root, "$.AppData.CredentialId", &ubuf[55], cridlen); + + //Credentional public key (COSE_KEY) + uint8_t coseKey[65] = {0}; + uint16_t cplen = n - 55 - cridlen; + if (verbose2) { + PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s", cplen, sprint_hex_inrow(&ubuf[55 + cridlen], cplen)); + } else { + PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s...", cplen, sprint_hex(&ubuf[55 + cridlen], MIN(cplen, 16))); + } + JsonSaveBufAsHexCompact(root, "$.AppData.COSE_KEY", &ubuf[55 + cridlen], cplen); + + if (showCBOR) { + PrintAndLog("COSE structure:"); + PrintAndLog("---------------- CBOR ------------------"); + TinyCborPrintFIDOPackage(fido2COSEKey, true, &ubuf[55 + cridlen], cplen); + PrintAndLog("---------------- CBOR ------------------"); + } + + res = COSEGetECDSAKey(&ubuf[55 + cridlen], cplen, verbose, coseKey); + if (res) { + PrintAndLog("ERROR: Can't get COSE_KEY."); + } else { + PrintAndLog("COSE public key: %s", sprint_hex_inrow(coseKey, sizeof(coseKey))); + JsonSaveBufAsHexCompact(root, "$.AppData.COSEPublicKey", coseKey, sizeof(coseKey)); + } + + free(ubuf); + + // attStmt - we are check only as DER certificate + int64_t alg = 0; + uint8_t sign[128] = {0}; + size_t signLen = 0; + uint8_t der[4097] = {0}; + size_t derLen = 0; + + res = CborMapGetKeyById(&parser, &map, data, dataLen, 3); + if (res) + return res; + + res = cbor_value_enter_container(&map, &mapsmt); + cbor_check(res); + + while (!cbor_value_at_end(&mapsmt)) { + char key[100] = {0}; + res = CborGetStringValue(&mapsmt, key, sizeof(key), &n); + cbor_check(res); + if (!strcmp(key, "alg")) { + cbor_value_get_int64(&mapsmt, &alg); + PrintAndLog("Alg [%lld] %s", (long long)alg, GetCOSEAlgDescription(alg)); + res = cbor_value_advance_fixed(&mapsmt); + cbor_check(res); + } + + if (!strcmp(key, "sig")) { + res = CborGetBinStringValue(&mapsmt, sign, sizeof(sign), &signLen); + cbor_check(res); + if (verbose2) { + PrintAndLog("signature [%d]: %s", signLen, sprint_hex_inrow(sign, signLen)); + } else { + PrintAndLog("signature [%d]: %s...", signLen, sprint_hex(sign, MIN(signLen, 16))); + } + } + + if (!strcmp(key, "x5c")) { + res = CborGetArrayBinStringValue(&mapsmt, der, sizeof(der), &derLen); + cbor_check(res); + if (verbose2) { + PrintAndLog("DER certificate[%d]:\n------------------DER-------------------", derLen); + dump_buffer_simple((const unsigned char *)der, derLen, NULL); + PrintAndLog("\n----------------DER---------------------"); + } else { + PrintAndLog("DER [%d]: %s...", derLen, sprint_hex(der, MIN(derLen, 16))); + } + JsonSaveBufAsHexCompact(root, "$.AppData.DER", der, derLen); + } + } + res = cbor_value_leave_container(&map, &mapsmt); + cbor_check(res); + + uint8_t public_key[65] = {0}; + + // print DER certificate in TLV view + if (showDERTLV) { + PrintAndLog("----------------DER TLV-----------------"); + asn1_print(der, derLen, " "); + PrintAndLog("----------------DER TLV-----------------"); + } + FIDOCheckDERAndGetKey(der, derLen, verbose, public_key, sizeof(public_key)); + JsonSaveBufAsHexCompact(root, "$.AppData.DERPublicKey", public_key, sizeof(public_key)); + + // check ANSI X9.62 format ECDSA signature (on P-256) + FIDO2CheckSignature(root, public_key, sign, signLen, authData, authDataLen, verbose); + + return 0; +} + +int FIDO2CreateGetAssertionReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen, bool createAllowList) { + if (datalen) + *datalen = 0; + if (!root || !data || !maxdatalen) + return 1; + + int res; + CborEncoder encoder; + CborEncoder map, array, mapint; + + cbor_encoder_init(&encoder, data, maxdatalen, 0); + + // create main map + res = cbor_encoder_create_map(&encoder, &map, createAllowList ? 4 : 3); + fido_check_if(res) { + // rpId + res = cbor_encode_uint(&map, 1); + fido_check_if(res) { + res = CBOREncodeElm(root, "$.RelyingPartyEntity.id", &map); + fido_check(res); + } + + // clientDataHash + res = cbor_encode_uint(&map, 2); + fido_check_if(res) { + res = CBOREncodeClientDataHash(root, &map); + fido_check(res); + } + + // allowList + if (createAllowList) { + res = cbor_encode_uint(&map, 3); + fido_check_if(res) { + res = cbor_encoder_create_array(&map, &array, 1); + fido_check_if(res) { + res = cbor_encoder_create_map(&array, &mapint, 2); + fido_check_if(res) { + res = cbor_encode_text_stringz(&mapint, "type"); + fido_check(res); + + res = cbor_encode_text_stringz(&mapint, "public-key"); + fido_check(res); + + res = cbor_encode_text_stringz(&mapint, "id"); + fido_check(res); + + res = CBOREncodeElm(root, "$.AppData.CredentialId", &mapint); + fido_check(res); + } + res = cbor_encoder_close_container(&array, &mapint); + fido_check(res); + } + res = cbor_encoder_close_container(&map, &array); + fido_check(res); + } + } + + // options + res = cbor_encode_uint(&map, 5); + fido_check_if(res) { + res = CBOREncodeElm(root, "GetAssertionOptions", &map); + fido_check(res); + } + } + res = cbor_encoder_close_container(&encoder, &map); + fido_check(res); + + size_t len = cbor_encoder_get_buffer_size(&encoder, data); + if (datalen) + *datalen = len; + + return 0; +} + +int FIDO2GetAssertionParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR) { + CborParser parser; + CborValue map, mapint; + int res; + uint8_t *ubuf; + size_t n; + + // credential + res = CborMapGetKeyById(&parser, &map, data, dataLen, 1); + if (res) + return res; + + res = cbor_value_enter_container(&map, &mapint); + cbor_check(res); + + while (!cbor_value_at_end(&mapint)) { + char key[100] = {0}; + res = CborGetStringValue(&mapint, key, sizeof(key), &n); + cbor_check(res); + + if (!strcmp(key, "type")) { + char ctype[200] = {0}; + res = CborGetStringValue(&mapint, ctype, sizeof(ctype), &n); + cbor_check(res); + PrintAndLog("credential type: %s", ctype); + } + + if (!strcmp(key, "id")) { + uint8_t cid[200] = {0}; + res = CborGetBinStringValue(&mapint, cid, sizeof(cid), &n); + cbor_check(res); + PrintAndLog("credential id [%d]: %s", n, sprint_hex(cid, n)); + } + } + res = cbor_value_leave_container(&map, &mapint); + cbor_check(res); + + // authData + uint8_t authData[400] = {0}; + size_t authDataLen = 0; + res = CborMapGetKeyById(&parser, &map, data, dataLen, 2); + if (res) + return res; + res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map); + cbor_check(res); + + authDataLen = n; + memcpy(authData, ubuf, authDataLen); + + if (verbose2) { + PrintAndLog("authData[%d]: %s", n, sprint_hex_inrow(authData, authDataLen)); + } else { + PrintAndLog("authData[%d]: %s...", n, sprint_hex(authData, MIN(authDataLen, 16))); + } + + PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf, 32)); + + // check RP ID Hash + if (CheckrpIdHash(root, ubuf)) { + PrintAndLog("rpIdHash OK."); + } else { + PrintAndLog("rpIdHash ERROR!"); + } + + PrintAndLog("Flags 0x%02x:", ubuf[32]); + if (!ubuf[32]) + PrintAndLog("none"); + if (ubuf[32] & 0x01) + PrintAndLog("up - user presence result"); + if (ubuf[32] & 0x04) + PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result"); + if (ubuf[32] & 0x40) + PrintAndLog("at - attested credential data included"); + if (ubuf[32] & 0x80) + PrintAndLog("ed - extension data included"); + + uint32_t cntr = (uint32_t)bytes_to_num(&ubuf[33], 4); + PrintAndLog("Counter: %d", cntr); + JsonSaveInt(root, "$.AppData.Counter", cntr); + + free(ubuf); + + // publicKeyCredentialUserEntity + res = CborMapGetKeyById(&parser, &map, data, dataLen, 4); + if (res) { + PrintAndLog("UserEntity n/a"); + } else { + res = cbor_value_enter_container(&map, &mapint); + cbor_check(res); + + while (!cbor_value_at_end(&mapint)) { + char key[100] = {0}; + res = CborGetStringValue(&mapint, key, sizeof(key), &n); + cbor_check(res); + + if (!strcmp(key, "name") || !strcmp(key, "displayName")) { + char cname[200] = {0}; + res = CborGetStringValue(&mapint, cname, sizeof(cname), &n); + cbor_check(res); + PrintAndLog("UserEntity %s: %s", key, cname); + } + + if (!strcmp(key, "id")) { + uint8_t cid[200] = {0}; + res = CborGetBinStringValue(&mapint, cid, sizeof(cid), &n); + cbor_check(res); + PrintAndLog("UserEntity id [%d]: %s", n, sprint_hex(cid, n)); + + // check + uint8_t idbuf[100] = {0}; + size_t idbuflen; + + JsonLoadBufAsHex(root, "$.UserEntity.id", idbuf, sizeof(idbuf), &idbuflen); + + if (idbuflen == n && !memcmp(idbuf, cid, idbuflen)) { + PrintAndLog("UserEntity id OK."); + } else { + PrintAndLog("ERROR: Wrong UserEntity id (from json: %s)", sprint_hex(idbuf, idbuflen)); + } + } + } + res = cbor_value_leave_container(&map, &mapint); + cbor_check(res); + } + + + // signature + res = CborMapGetKeyById(&parser, &map, data, dataLen, 3); + if (res) + return res; + res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map); + cbor_check(res); + + uint8_t *sign = ubuf; + size_t signLen = n; + + cbor_check(res); + if (verbose2) { + PrintAndLog("signature [%d]: %s", signLen, sprint_hex_inrow(sign, signLen)); + } else { + PrintAndLog("signature [%d]: %s...", signLen, sprint_hex(sign, MIN(signLen, 16))); + } + + // get public key from json + uint8_t PublicKey[65] = {0}; + size_t PublicKeyLen = 0; + JsonLoadBufAsHex(root, "$.AppData.COSEPublicKey", PublicKey, 65, &PublicKeyLen); + + // check ANSI X9.62 format ECDSA signature (on P-256) + FIDO2CheckSignature(root, PublicKey, sign, signLen, authData, authDataLen, verbose); + + free(ubuf); + + // numberOfCredentials + res = CborMapGetKeyById(&parser, &map, data, dataLen, 5); + if (res) { + PrintAndLog("numberOfCredentials: 1 by default"); + } else { + int64_t numberOfCredentials = 0; + cbor_value_get_int64(&map, &numberOfCredentials); + PrintAndLog("numberOfCredentials: %lld", (long long)numberOfCredentials); + } + + return 0; +} diff --git a/client/fido/fidocore.h b/client/fido/fidocore.h new file mode 100644 index 00000000..a1bcf876 --- /dev/null +++ b/client/fido/fidocore.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2018 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// FIDO2 authenticators core data and commands +// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html +//----------------------------------------------------------------------------- +// +#ifndef __FIDOCORE_H__ +#define __FIDOCORE_H__ + +#include +#include +#include +#include "cmdhf14a.h" +#include "emv/emvcore.h" + +typedef enum { + fido2CmdMakeCredential = 0x01, + fido2CmdGetAssertion = 0x02, + fido2CmdCancel = 0x03, + fido2CmdGetInfo = 0x04, + fido2CmdClientPIN = 0x06, + fido2CmdReset = 0x07, + fido2CmdGetNextAssertion = 0x08, + + // another data + fido2COSEKey = 0xF0 +} fido2Commands; + +typedef enum { + ptQuery, + ptResponse, +} fido2PacketType; + +extern int FIDOSelect(bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +extern int FIDOExchange(sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +extern int FIDORegister(uint8_t *params, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +extern int FIDOAuthentication(uint8_t *params, uint8_t paramslen, uint8_t controlb, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +extern int FIDO2GetInfo(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +extern int FIDO2MakeCredential(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +extern int FIDO2GetAssertion(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); + +extern int FIDOCheckDERAndGetKey(uint8_t *der, size_t derLen, bool verbose, uint8_t *publicKey, size_t publicKeyMaxLen); + +extern char *fido2GetCmdMemberDescription(uint8_t cmdCode, bool isResponse, int memberNum); +extern char *fido2GetCmdErrorDescription(uint8_t errorCode); + +extern bool CheckrpIdHash(json_t *json, uint8_t *hash); +extern int FIDO2CreateMakeCredentionalReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen); +extern int FIDO2MakeCredentionalParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR, bool showDERTLV); +extern int FIDO2CreateGetAssertionReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen, bool createAllowList); +extern int FIDO2GetAssertionParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR); + +#endif /* __FIDOCORE_H__ */ diff --git a/client/tinycbor/Makefile b/client/tinycbor/Makefile new file mode 100644 index 00000000..78ef64dc --- /dev/null +++ b/client/tinycbor/Makefile @@ -0,0 +1,46 @@ + +LIB_A = tinycbor.a +tinycbor_SOURCES = \ + cborencoder.c \ + cborencoder_close_container_checked.c \ + cborerrorstrings.c \ + cborparser.c \ + cborparser_dup_string.c \ + cborpretty.c \ + cbortojson.c \ + cborvalidation.c \ + +CFILES = $(filter %.c, $(tinycbor_SOURCES)) +CMDOBJS = $(CFILES:%.c=%.o) +CLEAN = $(CMDOBJS) + +CC= gcc +CFLAGS= -O2 -Wall -Wno-unused-variable -Wno-unused-function +LIBS= $(SYSLIBS) $(MYLIBS) +DEFAULT_INCLUDES = -I. -I.. +DEFS = -DHAVE_STDINT_H + +AR= ar rcs +RANLIB= ranlib +RM= rm -f +TST= echo + +SYSLDFLAGS= +SYSLIBS= + +MYLIBS= +MYOBJS= + +all: $(CMDOBJS) + $(AR) $(LIB_A) $(CMDOBJS) + $(RANLIB) $(LIB_A) + +clean: + $(RM) $(CLEAN) + $(RM) $(LIB_A) + +%.o: %.c + $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(CFLAGS) -c -o $@ $< $(LIBS) + +.PHONY: all clean + diff --git a/client/tinycbor/cbor.h b/client/tinycbor/cbor.h new file mode 100644 index 00000000..fd145be2 --- /dev/null +++ b/client/tinycbor/cbor.h @@ -0,0 +1,606 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef CBOR_H +#define CBOR_H + +#ifndef assert +#include +#endif +#include +#include +#include +#include +#include + +#include "tinycbor-version.h" + +#define TINYCBOR_VERSION ((TINYCBOR_VERSION_MAJOR << 16) | (TINYCBOR_VERSION_MINOR << 8) | TINYCBOR_VERSION_PATCH) + +#ifdef __cplusplus +extern "C" { +#else +#include +#endif + +#ifndef SIZE_MAX +/* Some systems fail to define SIZE_MAX in , even though C99 requires it... + * Conversion from signed to unsigned is defined in 6.3.1.3 (Signed and unsigned integers) p2, + * which says: "the value is converted by repeatedly adding or subtracting one more than the + * maximum value that can be represented in the new type until the value is in the range of the + * new type." + * So -1 gets converted to size_t by adding SIZE_MAX + 1, which results in SIZE_MAX. + */ +# define SIZE_MAX ((size_t)-1) +#endif + +#ifndef CBOR_API +# define CBOR_API +#endif +#ifndef CBOR_PRIVATE_API +# define CBOR_PRIVATE_API +#endif +#ifndef CBOR_INLINE_API +# if defined(__cplusplus) +# define CBOR_INLINE inline +# define CBOR_INLINE_API inline +# else +# define CBOR_INLINE_API static CBOR_INLINE +# if defined(_MSC_VER) +# define CBOR_INLINE __inline +# elif defined(__GNUC__) +# define CBOR_INLINE __inline__ +# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +# define CBOR_INLINE inline +# else +# define CBOR_INLINE +# endif +# endif +#endif + +typedef enum CborType { + CborIntegerType = 0x00, + CborByteStringType = 0x40, + CborTextStringType = 0x60, + CborArrayType = 0x80, + CborMapType = 0xa0, + CborTagType = 0xc0, + CborSimpleType = 0xe0, + CborBooleanType = 0xf5, + CborNullType = 0xf6, + CborUndefinedType = 0xf7, + CborHalfFloatType = 0xf9, + CborFloatType = 0xfa, + CborDoubleType = 0xfb, + + CborInvalidType = 0xff /* equivalent to the break byte, so it will never be used */ +} CborType; + +typedef uint64_t CborTag; +typedef enum CborKnownTags { + CborDateTimeStringTag = 0, + CborUnixTime_tTag = 1, + CborPositiveBignumTag = 2, + CborNegativeBignumTag = 3, + CborDecimalTag = 4, + CborBigfloatTag = 5, + CborCOSE_Encrypt0Tag = 16, + CborCOSE_Mac0Tag = 17, + CborCOSE_Sign1Tag = 18, + CborExpectedBase64urlTag = 21, + CborExpectedBase64Tag = 22, + CborExpectedBase16Tag = 23, + CborEncodedCborTag = 24, + CborUrlTag = 32, + CborBase64urlTag = 33, + CborBase64Tag = 34, + CborRegularExpressionTag = 35, + CborMimeMessageTag = 36, + CborCOSE_EncryptTag = 96, + CborCOSE_MacTag = 97, + CborCOSE_SignTag = 98, + CborSignatureTag = 55799 +} CborKnownTags; + +/* #define the constants so we can check with #ifdef */ +#define CborDateTimeStringTag CborDateTimeStringTag +#define CborUnixTime_tTag CborUnixTime_tTag +#define CborPositiveBignumTag CborPositiveBignumTag +#define CborNegativeBignumTag CborNegativeBignumTag +#define CborDecimalTag CborDecimalTag +#define CborBigfloatTag CborBigfloatTag +#define CborCOSE_Encrypt0Tag CborCOSE_Encrypt0Tag +#define CborCOSE_Mac0Tag CborCOSE_Mac0Tag +#define CborCOSE_Sign1Tag CborCOSE_Sign1Tag +#define CborExpectedBase64urlTag CborExpectedBase64urlTag +#define CborExpectedBase64Tag CborExpectedBase64Tag +#define CborExpectedBase16Tag CborExpectedBase16Tag +#define CborEncodedCborTag CborEncodedCborTag +#define CborUrlTag CborUrlTag +#define CborBase64urlTag CborBase64urlTag +#define CborBase64Tag CborBase64Tag +#define CborRegularExpressionTag CborRegularExpressionTag +#define CborMimeMessageTag CborMimeMessageTag +#define CborCOSE_EncryptTag CborCOSE_EncryptTag +#define CborCOSE_MacTag CborCOSE_MacTag +#define CborCOSE_SignTag CborCOSE_SignTag +#define CborSignatureTag CborSignatureTag + +/* Error API */ + +typedef enum CborError { + CborNoError = 0, + + /* errors in all modes */ + CborUnknownError, + CborErrorUnknownLength, /* request for length in array, map, or string with indeterminate length */ + CborErrorAdvancePastEOF, + CborErrorIO, + + /* parser errors streaming errors */ + CborErrorGarbageAtEnd = 256, + CborErrorUnexpectedEOF, + CborErrorUnexpectedBreak, + CborErrorUnknownType, /* can only happen in major type 7 */ + CborErrorIllegalType, /* type not allowed here */ + CborErrorIllegalNumber, + CborErrorIllegalSimpleType, /* types of value less than 32 encoded in two bytes */ + + /* parser errors in strict mode parsing only */ + CborErrorUnknownSimpleType = 512, + CborErrorUnknownTag, + CborErrorInappropriateTagForType, + CborErrorDuplicateObjectKeys, + CborErrorInvalidUtf8TextString, + CborErrorExcludedType, + CborErrorExcludedValue, + CborErrorImproperValue, + CborErrorOverlongEncoding, + CborErrorMapKeyNotString, + CborErrorMapNotSorted, + CborErrorMapKeysNotUnique, + + /* encoder errors */ + CborErrorTooManyItems = 768, + CborErrorTooFewItems, + + /* internal implementation errors */ + CborErrorDataTooLarge = 1024, + CborErrorNestingTooDeep, + CborErrorUnsupportedType, + + /* errors in converting to JSON */ + CborErrorJsonObjectKeyIsAggregate = 1280, + CborErrorJsonObjectKeyNotString, + CborErrorJsonNotImplemented, + + CborErrorOutOfMemory = (int) (~0U / 2 + 1), + CborErrorInternalError = (int) (~0U / 2) /* INT_MAX on two's complement machines */ +} CborError; + +CBOR_API const char *cbor_error_string(CborError error); + +/* Encoder API */ +struct CborEncoder +{ + union { + uint8_t *ptr; + ptrdiff_t bytes_needed; + } data; + const uint8_t *end; + size_t remaining; + int flags; +}; +typedef struct CborEncoder CborEncoder; + +static const size_t CborIndefiniteLength = SIZE_MAX; + +CBOR_API void cbor_encoder_init(CborEncoder *encoder, uint8_t *buffer, size_t size, int flags); +CBOR_API CborError cbor_encode_uint(CborEncoder *encoder, uint64_t value); +CBOR_API CborError cbor_encode_int(CborEncoder *encoder, int64_t value); +CBOR_API CborError cbor_encode_negative_int(CborEncoder *encoder, uint64_t absolute_value); +CBOR_API CborError cbor_encode_simple_value(CborEncoder *encoder, uint8_t value); +CBOR_API CborError cbor_encode_tag(CborEncoder *encoder, CborTag tag); +CBOR_API CborError cbor_encode_text_string(CborEncoder *encoder, const char *string, size_t length); +CBOR_INLINE_API CborError cbor_encode_text_stringz(CborEncoder *encoder, const char *string) +{ return cbor_encode_text_string(encoder, string, strlen(string)); } +CBOR_API CborError cbor_encode_byte_string(CborEncoder *encoder, const uint8_t *string, size_t length); +CBOR_API CborError cbor_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value); + +CBOR_INLINE_API CborError cbor_encode_boolean(CborEncoder *encoder, bool value) +{ return cbor_encode_simple_value(encoder, (int)value - 1 + (CborBooleanType & 0x1f)); } +CBOR_INLINE_API CborError cbor_encode_null(CborEncoder *encoder) +{ return cbor_encode_simple_value(encoder, CborNullType & 0x1f); } +CBOR_INLINE_API CborError cbor_encode_undefined(CborEncoder *encoder) +{ return cbor_encode_simple_value(encoder, CborUndefinedType & 0x1f); } + +CBOR_INLINE_API CborError cbor_encode_half_float(CborEncoder *encoder, const void *value) +{ return cbor_encode_floating_point(encoder, CborHalfFloatType, value); } +CBOR_INLINE_API CborError cbor_encode_float(CborEncoder *encoder, float value) +{ return cbor_encode_floating_point(encoder, CborFloatType, &value); } +CBOR_INLINE_API CborError cbor_encode_double(CborEncoder *encoder, double value) +{ return cbor_encode_floating_point(encoder, CborDoubleType, &value); } + +CBOR_API CborError cbor_encoder_create_array(CborEncoder *encoder, CborEncoder *arrayEncoder, size_t length); +CBOR_API CborError cbor_encoder_create_map(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length); +CBOR_API CborError cbor_encoder_close_container(CborEncoder *encoder, const CborEncoder *containerEncoder); +CBOR_API CborError cbor_encoder_close_container_checked(CborEncoder *encoder, const CborEncoder *containerEncoder); + +CBOR_INLINE_API uint8_t *_cbor_encoder_get_buffer_pointer(const CborEncoder *encoder) +{ + return encoder->data.ptr; +} + +CBOR_INLINE_API size_t cbor_encoder_get_buffer_size(const CborEncoder *encoder, const uint8_t *buffer) +{ + return (size_t)(encoder->data.ptr - buffer); +} + +CBOR_INLINE_API size_t cbor_encoder_get_extra_bytes_needed(const CborEncoder *encoder) +{ + return encoder->end ? 0 : (size_t)encoder->data.bytes_needed; +} + +/* Parser API */ + +enum CborParserIteratorFlags +{ + CborIteratorFlag_IntegerValueTooLarge = 0x01, + CborIteratorFlag_NegativeInteger = 0x02, + CborIteratorFlag_IteratingStringChunks = 0x02, + CborIteratorFlag_UnknownLength = 0x04, + CborIteratorFlag_ContainerIsMap = 0x20 +}; + +struct CborParser +{ + const uint8_t *end; + uint32_t flags; +}; +typedef struct CborParser CborParser; + +struct CborValue +{ + const CborParser *parser; + const uint8_t *ptr; + uint32_t remaining; + uint16_t extra; + uint8_t type; + uint8_t flags; +}; +typedef struct CborValue CborValue; + +CBOR_API CborError cbor_parser_init(const uint8_t *buffer, size_t size, uint32_t flags, CborParser *parser, CborValue *it); + +CBOR_API CborError cbor_value_validate_basic(const CborValue *it); + +CBOR_INLINE_API bool cbor_value_at_end(const CborValue *it) +{ return it->remaining == 0; } +CBOR_INLINE_API const uint8_t *cbor_value_get_next_byte(const CborValue *it) +{ return it->ptr; } +CBOR_API CborError cbor_value_advance_fixed(CborValue *it); +CBOR_API CborError cbor_value_advance(CborValue *it); +CBOR_INLINE_API bool cbor_value_is_container(const CborValue *it) +{ return it->type == CborArrayType || it->type == CborMapType; } +CBOR_API CborError cbor_value_enter_container(const CborValue *it, CborValue *recursed); +CBOR_API CborError cbor_value_leave_container(CborValue *it, const CborValue *recursed); + +CBOR_PRIVATE_API uint64_t _cbor_value_decode_int64_internal(const CborValue *value); +CBOR_INLINE_API uint64_t _cbor_value_extract_int64_helper(const CborValue *value) +{ + return value->flags & CborIteratorFlag_IntegerValueTooLarge ? + _cbor_value_decode_int64_internal(value) : value->extra; +} + +CBOR_INLINE_API bool cbor_value_is_valid(const CborValue *value) +{ return value && value->type != CborInvalidType; } +CBOR_INLINE_API CborType cbor_value_get_type(const CborValue *value) +{ return (CborType)value->type; } + +/* Null & undefined type */ +CBOR_INLINE_API bool cbor_value_is_null(const CborValue *value) +{ return value->type == CborNullType; } +CBOR_INLINE_API bool cbor_value_is_undefined(const CborValue *value) +{ return value->type == CborUndefinedType; } + +/* Booleans */ +CBOR_INLINE_API bool cbor_value_is_boolean(const CborValue *value) +{ return value->type == CborBooleanType; } +CBOR_INLINE_API CborError cbor_value_get_boolean(const CborValue *value, bool *result) +{ + assert(cbor_value_is_boolean(value)); + *result = !!value->extra; + return CborNoError; +} + +/* Simple types */ +CBOR_INLINE_API bool cbor_value_is_simple_type(const CborValue *value) +{ return value->type == CborSimpleType; } +CBOR_INLINE_API CborError cbor_value_get_simple_type(const CborValue *value, uint8_t *result) +{ + assert(cbor_value_is_simple_type(value)); + *result = (uint8_t)value->extra; + return CborNoError; +} + +/* Integers */ +CBOR_INLINE_API bool cbor_value_is_integer(const CborValue *value) +{ return value->type == CborIntegerType; } +CBOR_INLINE_API bool cbor_value_is_unsigned_integer(const CborValue *value) +{ return cbor_value_is_integer(value) && (value->flags & CborIteratorFlag_NegativeInteger) == 0; } +CBOR_INLINE_API bool cbor_value_is_negative_integer(const CborValue *value) +{ return cbor_value_is_integer(value) && (value->flags & CborIteratorFlag_NegativeInteger); } + +CBOR_INLINE_API CborError cbor_value_get_raw_integer(const CborValue *value, uint64_t *result) +{ + assert(cbor_value_is_integer(value)); + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_uint64(const CborValue *value, uint64_t *result) +{ + assert(cbor_value_is_unsigned_integer(value)); + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_int64(const CborValue *value, int64_t *result) +{ + assert(cbor_value_is_integer(value)); + *result = (int64_t) _cbor_value_extract_int64_helper(value); + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_int(const CborValue *value, int *result) +{ + assert(cbor_value_is_integer(value)); + *result = (int) _cbor_value_extract_int64_helper(value); + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +CBOR_API CborError cbor_value_get_int64_checked(const CborValue *value, int64_t *result); +CBOR_API CborError cbor_value_get_int_checked(const CborValue *value, int *result); + +CBOR_INLINE_API bool cbor_value_is_length_known(const CborValue *value) +{ return (value->flags & CborIteratorFlag_UnknownLength) == 0; } + +/* Tags */ +CBOR_INLINE_API bool cbor_value_is_tag(const CborValue *value) +{ return value->type == CborTagType; } +CBOR_INLINE_API CborError cbor_value_get_tag(const CborValue *value, CborTag *result) +{ + assert(cbor_value_is_tag(value)); + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} +CBOR_API CborError cbor_value_skip_tag(CborValue *it); + +/* Strings */ +CBOR_INLINE_API bool cbor_value_is_byte_string(const CborValue *value) +{ return value->type == CborByteStringType; } +CBOR_INLINE_API bool cbor_value_is_text_string(const CborValue *value) +{ return value->type == CborTextStringType; } + +CBOR_INLINE_API CborError cbor_value_get_string_length(const CborValue *value, size_t *length) +{ + uint64_t v; + assert(cbor_value_is_byte_string(value) || cbor_value_is_text_string(value)); + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_PRIVATE_API CborError _cbor_value_copy_string(const CborValue *value, void *buffer, + size_t *buflen, CborValue *next); +CBOR_PRIVATE_API CborError _cbor_value_dup_string(const CborValue *value, void **buffer, + size_t *buflen, CborValue *next); + +CBOR_API CborError cbor_value_calculate_string_length(const CborValue *value, size_t *length); + +CBOR_INLINE_API CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_text_string(value)); + return _cbor_value_copy_string(value, buffer, buflen, next); +} +CBOR_INLINE_API CborError cbor_value_copy_byte_string(const CborValue *value, uint8_t *buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_byte_string(value)); + return _cbor_value_copy_string(value, buffer, buflen, next); +} + +CBOR_INLINE_API CborError cbor_value_dup_text_string(const CborValue *value, char **buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_text_string(value)); + return _cbor_value_dup_string(value, (void **)buffer, buflen, next); +} +CBOR_INLINE_API CborError cbor_value_dup_byte_string(const CborValue *value, uint8_t **buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_byte_string(value)); + return _cbor_value_dup_string(value, (void **)buffer, buflen, next); +} + +CBOR_API CborError cbor_value_text_string_equals(const CborValue *value, const char *string, bool *result); + +/* Maps and arrays */ +CBOR_INLINE_API bool cbor_value_is_array(const CborValue *value) +{ return value->type == CborArrayType; } +CBOR_INLINE_API bool cbor_value_is_map(const CborValue *value) +{ return value->type == CborMapType; } + +CBOR_INLINE_API CborError cbor_value_get_array_length(const CborValue *value, size_t *length) +{ + uint64_t v; + assert(cbor_value_is_array(value)); + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_map_length(const CborValue *value, size_t *length) +{ + uint64_t v; + assert(cbor_value_is_map(value)); + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_API CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element); + +/* Floating point */ +CBOR_INLINE_API bool cbor_value_is_half_float(const CborValue *value) +{ return value->type == CborHalfFloatType; } +CBOR_API CborError cbor_value_get_half_float(const CborValue *value, void *result); + +CBOR_INLINE_API bool cbor_value_is_float(const CborValue *value) +{ return value->type == CborFloatType; } +CBOR_INLINE_API CborError cbor_value_get_float(const CborValue *value, float *result) +{ + uint32_t data; + assert(cbor_value_is_float(value)); + assert(value->flags & CborIteratorFlag_IntegerValueTooLarge); + data = (uint32_t)_cbor_value_decode_int64_internal(value); + memcpy(result, &data, sizeof(*result)); + return CborNoError; +} + +CBOR_INLINE_API bool cbor_value_is_double(const CborValue *value) +{ return value->type == CborDoubleType; } +CBOR_INLINE_API CborError cbor_value_get_double(const CborValue *value, double *result) +{ + uint64_t data; + assert(cbor_value_is_double(value)); + assert(value->flags & CborIteratorFlag_IntegerValueTooLarge); + data = _cbor_value_decode_int64_internal(value); + memcpy(result, &data, sizeof(*result)); + return CborNoError; +} + +/* Validation API */ + +enum CborValidationFlags { + /* Bit mapping: + * bits 0-7 (8 bits): canonical format + * bits 8-11 (4 bits): canonical format & strict mode + * bits 12-20 (8 bits): strict mode + * bits 21-31 (10 bits): other + */ + + CborValidateShortestIntegrals = 0x0001, + CborValidateShortestFloatingPoint = 0x0002, + CborValidateShortestNumbers = CborValidateShortestIntegrals | CborValidateShortestFloatingPoint, + CborValidateNoIndeterminateLength = 0x0100, + CborValidateMapIsSorted = 0x0200 | CborValidateNoIndeterminateLength, + + CborValidateCanonicalFormat = 0x0fff, + + CborValidateMapKeysAreUnique = 0x1000 | CborValidateMapIsSorted, + CborValidateTagUse = 0x2000, + CborValidateUtf8 = 0x4000, + + CborValidateStrictMode = 0xfff00, + + CborValidateMapKeysAreString = 0x100000, + CborValidateNoUndefined = 0x200000, + CborValidateNoTags = 0x400000, + CborValidateFiniteFloatingPoint = 0x800000, + /* unused = 0x1000000, */ + /* unused = 0x2000000, */ + + CborValidateNoUnknownSimpleTypesSA = 0x4000000, + CborValidateNoUnknownSimpleTypes = 0x8000000 | CborValidateNoUnknownSimpleTypesSA, + CborValidateNoUnknownTagsSA = 0x10000000, + CborValidateNoUnknownTagsSR = 0x20000000 | CborValidateNoUnknownTagsSA, + CborValidateNoUnknownTags = 0x40000000 | CborValidateNoUnknownTagsSR, + + CborValidateCompleteData = (int)0x80000000, + + CborValidateStrictest = (int)~0U, + CborValidateBasic = 0 +}; + +CBOR_API CborError cbor_value_validate(const CborValue *it, uint32_t flags); + +/* Human-readable (dump) API */ + +enum CborPrettyFlags { + CborPrettyNumericEncodingIndicators = 0x01, + CborPrettyTextualEncodingIndicators = 0, + + CborPrettyIndicateIndeterminateLength = 0x02, + CborPrettyIndicateIndetermineLength = CborPrettyIndicateIndeterminateLength, /* deprecated */ + CborPrettyIndicateOverlongNumbers = 0x04, + + CborPrettyShowStringFragments = 0x100, + CborPrettyMergeStringFragments = 0, + + CborPrettyDefaultFlags = CborPrettyIndicateIndeterminateLength +}; + +typedef CborError (*CborStreamFunction)(void *token, const char *fmt, ...) +#ifdef __GNUC__ + __attribute__((__format__(printf, 2, 3))) +#endif +; + +CBOR_API CborError cbor_value_to_pretty_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags); + +/* The following API requires a hosted C implementation (uses FILE*) */ +#if !defined(__STDC_HOSTED__) || __STDC_HOSTED__-0 == 1 +CBOR_API CborError cbor_value_to_pretty_advance_flags(FILE *out, CborValue *value, int flags); +CBOR_API CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value); +CBOR_INLINE_API CborError cbor_value_to_pretty(FILE *out, const CborValue *value) +{ + CborValue copy = *value; + return cbor_value_to_pretty_advance_flags(out, ©, CborPrettyDefaultFlags); +} +#endif /* __STDC_HOSTED__ check */ + +#ifdef __cplusplus +} +#endif + +#endif /* CBOR_H */ + diff --git a/client/tinycbor/cborencoder.c b/client/tinycbor/cborencoder.c new file mode 100644 index 00000000..adb92fd8 --- /dev/null +++ b/client/tinycbor/cborencoder.c @@ -0,0 +1,645 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef _BSD_SOURCE +#define _BSD_SOURCE 1 +#endif +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE 1 +#endif +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" + +#include +#include + +/** + * \defgroup CborEncoding Encoding to CBOR + * \brief Group of functions used to encode data to CBOR. + * + * CborEncoder is used to encode data into a CBOR stream. The outermost + * CborEncoder is initialized by calling cbor_encoder_init(), with the buffer + * where the CBOR stream will be stored. The outermost CborEncoder is usually + * used to encode exactly one item, most often an array or map. It is possible + * to encode more than one item, but care must then be taken on the decoder + * side to ensure the state is reset after each item was decoded. + * + * Nested CborEncoder objects are created using cbor_encoder_create_array() and + * cbor_encoder_create_map(), later closed with cbor_encoder_close_container() + * or cbor_encoder_close_container_checked(). The pairs of creation and closing + * must be exactly matched and their parameters are always the same. + * + * CborEncoder writes directly to the user-supplied buffer, without extra + * buffering. CborEncoder does not allocate memory and CborEncoder objects are + * usually created on the stack of the encoding functions. + * + * The example below initializes a CborEncoder object with a buffer and encodes + * a single integer. + * + * \code + * uint8_t buf[16]; + * CborEncoder encoder; + * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); + * cbor_encode_int(&encoder, some_value); + * \endcode + * + * As explained before, usually the outermost CborEncoder object is used to add + * one array or map, which in turn contains multiple elements. The example + * below creates a CBOR map with one element: a key "foo" and a boolean value. + * + * \code + * uint8_t buf[16]; + * CborEncoder encoder, mapEncoder; + * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); + * cbor_encoder_create_map(&encoder, &mapEncoder, 1); + * cbor_encode_text_stringz(&mapEncoder, "foo"); + * cbor_encode_boolean(&mapEncoder, some_value); + * cbor_encoder_close_container(&encoder, &mapEncoder); + * \endcode + * + *

Error checking and buffer size

+ * + * All functions operating on CborEncoder return a condition of type CborError. + * If the encoding was successful, they return CborNoError. Some functions do + * extra checking on the input provided and may return some other error + * conditions (for example, cbor_encode_simple_value() checks that the type is + * of the correct type). + * + * In addition, all functions check whether the buffer has enough bytes to + * encode the item being appended. If that is not possible, they return + * CborErrorOutOfMemory. + * + * It is possible to continue with the encoding of data past the first function + * that returns CborErrorOutOfMemory. CborEncoder functions will not overrun + * the buffer, but will instead count how many more bytes are needed to + * complete the encoding. At the end, you can obtain that count by calling + * cbor_encoder_get_extra_bytes_needed(). + * + * \section1 Finalizing the encoding + * + * Once all items have been appended and the containers have all been properly + * closed, the user-supplied buffer will contain the CBOR stream and may be + * immediately used. To obtain the size of the buffer, call + * cbor_encoder_get_buffer_size() with the original buffer pointer. + * + * The example below illustrates how one can encode an item with error checking + * and then pass on the buffer for network sending. + * + * \code + * uint8_t buf[16]; + * CborError err; + * CborEncoder encoder, mapEncoder; + * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); + * err = cbor_encoder_create_map(&encoder, &mapEncoder, 1); + * if (!err) + * return err; + * err = cbor_encode_text_stringz(&mapEncoder, "foo"); + * if (!err) + * return err; + * err = cbor_encode_boolean(&mapEncoder, some_value); + * if (!err) + * return err; + * err = cbor_encoder_close_container_checked(&encoder, &mapEncoder); + * if (!err) + * return err; + * + * size_t len = cbor_encoder_get_buffer_size(&encoder, buf); + * send_payload(buf, len); + * return CborNoError; + * \endcode + * + * Finally, the example below expands on the one above and also + * deals with dynamically growing the buffer if the initial allocation wasn't + * big enough. Note the two places where the error checking was replaced with + * an cbor_assertion, showing where the author assumes no error can occur. + * + * \code + * uint8_t *encode_string_array(const char **strings, int n, size_t *bufsize) + * { + * CborError err; + * CborEncoder encoder, arrayEncoder; + * size_t size = 256; + * uint8_t *buf = NULL; + * + * while (1) { + * int i; + * size_t more_bytes; + * uint8_t *nbuf = realloc(buf, size); + * if (nbuf == NULL) + * goto error; + * buf = nbuf; + * + * cbor_encoder_init(&encoder, &buf, size, 0); + * err = cbor_encoder_create_array(&encoder, &arrayEncoder, n); + * cbor_assert(err); // can't fail, the buffer is always big enough + * + * for (i = 0; i < n; ++i) { + * err = cbor_encode_text_stringz(&arrayEncoder, strings[i]); + * if (err && err != CborErrorOutOfMemory) + * goto error; + * } + * + * err = cbor_encoder_close_container_checked(&encoder, &arrayEncoder); + * cbor_assert(err); // shouldn't fail! + * + * more_bytes = cbor_encoder_get_extra_bytes_needed(encoder); + * if (more_size) { + * // buffer wasn't big enough, try again + * size += more_bytes; + * continue; + * } + * + * *bufsize = cbor_encoder_get_buffer_size(encoder, buf); + * return buf; + * } + * error: + * free(buf); + * return NULL; + * } + * \endcode + */ + +/** + * \addtogroup CborEncoding + * @{ + */ + +/** + * \struct CborEncoder + * Structure used to encode to CBOR. + */ + +/** + * Initializes a CborEncoder structure \a encoder by pointing it to buffer \a + * buffer of size \a size. The \a flags field is currently unused and must be + * zero. + */ +void cbor_encoder_init(CborEncoder *encoder, uint8_t *buffer, size_t size, int flags) +{ + encoder->data.ptr = buffer; + encoder->end = buffer + size; + encoder->remaining = 2; + encoder->flags = flags; +} + +static inline void put16(void *where, uint16_t v) +{ + v = cbor_htons(v); + memcpy(where, &v, sizeof(v)); +} + +/* Note: Since this is currently only used in situations where OOM is the only + * valid error, we KNOW this to be true. Thus, this function now returns just 'true', + * but if in the future, any function starts returning a non-OOM error, this will need + * to be changed to the test. At the moment, this is done to prevent more branches + * being created in the tinycbor output */ +static inline bool isOomError(CborError err) +{ + (void) err; + return true; +} + +static inline void put32(void *where, uint32_t v) +{ + v = cbor_htonl(v); + memcpy(where, &v, sizeof(v)); +} + +static inline void put64(void *where, uint64_t v) +{ + v = cbor_htonll(v); + memcpy(where, &v, sizeof(v)); +} + +static inline bool would_overflow(CborEncoder *encoder, size_t len) +{ + ptrdiff_t remaining = (ptrdiff_t)encoder->end; + remaining -= remaining ? (ptrdiff_t)encoder->data.ptr : encoder->data.bytes_needed; + remaining -= (ptrdiff_t)len; + return unlikely(remaining < 0); +} + +static inline void advance_ptr(CborEncoder *encoder, size_t n) +{ + if (encoder->end) + encoder->data.ptr += n; + else + encoder->data.bytes_needed += n; +} + +static inline CborError append_to_buffer(CborEncoder *encoder, const void *data, size_t len) +{ + if (would_overflow(encoder, len)) { + if (encoder->end != NULL) { + len -= encoder->end - encoder->data.ptr; + encoder->end = NULL; + encoder->data.bytes_needed = 0; + } + + advance_ptr(encoder, len); + return CborErrorOutOfMemory; + } + + memcpy(encoder->data.ptr, data, len); + encoder->data.ptr += len; + return CborNoError; +} + +static inline CborError append_byte_to_buffer(CborEncoder *encoder, uint8_t byte) +{ + return append_to_buffer(encoder, &byte, 1); +} + +static inline CborError encode_number_no_update(CborEncoder *encoder, uint64_t ui, uint8_t shiftedMajorType) +{ + /* Little-endian would have been so much more convenient here: + * We could just write at the beginning of buf but append_to_buffer + * only the necessary bytes. + * Since it has to be big endian, do it the other way around: + * write from the end. */ + uint64_t buf[2]; + uint8_t *const bufend = (uint8_t *)buf + sizeof(buf); + uint8_t *bufstart = bufend - 1; + put64(buf + 1, ui); /* we probably have a bunch of zeros in the beginning */ + + if (ui < Value8Bit) { + *bufstart += shiftedMajorType; + } else { + uint8_t more = 0; + if (ui > 0xffU) + ++more; + if (ui > 0xffffU) + ++more; + if (ui > 0xffffffffU) + ++more; + bufstart -= (size_t)1 << more; + *bufstart = shiftedMajorType + Value8Bit + more; + } + + return append_to_buffer(encoder, bufstart, bufend - bufstart); +} + +static inline void saturated_decrement(CborEncoder *encoder) +{ + if (encoder->remaining) + --encoder->remaining; +} + +static inline CborError encode_number(CborEncoder *encoder, uint64_t ui, uint8_t shiftedMajorType) +{ + saturated_decrement(encoder); + return encode_number_no_update(encoder, ui, shiftedMajorType); +} + +/** + * Appends the unsigned 64-bit integer \a value to the CBOR stream provided by + * \a encoder. + * + * \sa cbor_encode_negative_int, cbor_encode_int + */ +CborError cbor_encode_uint(CborEncoder *encoder, uint64_t value) +{ + return encode_number(encoder, value, UnsignedIntegerType << MajorTypeShift); +} + +/** + * Appends the negative 64-bit integer whose absolute value is \a + * absolute_value to the CBOR stream provided by \a encoder. + * + * If the value \a absolute_value is zero, this function encodes -2^64. + * + * \sa cbor_encode_uint, cbor_encode_int + */ +CborError cbor_encode_negative_int(CborEncoder *encoder, uint64_t absolute_value) +{ + return encode_number(encoder, absolute_value - 1, NegativeIntegerType << MajorTypeShift); +} + +/** + * Appends the signed 64-bit integer \a value to the CBOR stream provided by + * \a encoder. + * + * \sa cbor_encode_negative_int, cbor_encode_uint + */ +CborError cbor_encode_int(CborEncoder *encoder, int64_t value) +{ + /* adapted from code in RFC 7049 appendix C (pseudocode) */ + uint64_t ui = value >> 63; /* extend sign to whole length */ + uint8_t majorType = ui & 0x20; /* extract major type */ + ui ^= value; /* complement negatives */ + return encode_number(encoder, ui, majorType); +} + +/** + * Appends the CBOR Simple Type of value \a value to the CBOR stream provided by + * \a encoder. + * + * This function may return error CborErrorIllegalSimpleType if the \a value + * variable contains a number that is not a valid simple type. + */ +CborError cbor_encode_simple_value(CborEncoder *encoder, uint8_t value) +{ +#ifndef CBOR_ENCODER_NO_CHECK_USER + /* check if this is a valid simple type */ + if (value >= HalfPrecisionFloat && value <= Break) + return CborErrorIllegalSimpleType; +#endif + return encode_number(encoder, value, SimpleTypesType << MajorTypeShift); +} + +/** + * Appends the floating-point value of type \a fpType and pointed to by \a + * value to the CBOR stream provided by \a encoder. The value of \a fpType must + * be one of CborHalfFloatType, CborFloatType or CborDoubleType, otherwise the + * behavior of this function is undefined. + * + * This function is useful for code that needs to pass through floating point + * values but does not wish to have the actual floating-point code. + * + * \sa cbor_encode_half_float, cbor_encode_float, cbor_encode_double + */ +CborError cbor_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value) +{ + unsigned size; + uint8_t buf[1 + sizeof(uint64_t)]; + cbor_assert(fpType == CborHalfFloatType || fpType == CborFloatType || fpType == CborDoubleType); + buf[0] = fpType; + + size = 2U << (fpType - CborHalfFloatType); + if (size == 8) + put64(buf + 1, *(const uint64_t*)value); + else if (size == 4) + put32(buf + 1, *(const uint32_t*)value); + else + put16(buf + 1, *(const uint16_t*)value); + saturated_decrement(encoder); + return append_to_buffer(encoder, buf, size + 1); +} + +/** + * Appends the CBOR tag \a tag to the CBOR stream provided by \a encoder. + * + * \sa CborTag + */ +CborError cbor_encode_tag(CborEncoder *encoder, CborTag tag) +{ + /* tags don't count towards the number of elements in an array or map */ + return encode_number_no_update(encoder, tag, TagType << MajorTypeShift); +} + +static CborError encode_string(CborEncoder *encoder, size_t length, uint8_t shiftedMajorType, const void *string) +{ + CborError err = encode_number(encoder, length, shiftedMajorType); + if (err && !isOomError(err)) + return err; + return append_to_buffer(encoder, string, length); +} + +/** + * \fn CborError cbor_encode_text_stringz(CborEncoder *encoder, const char *string) + * + * Appends the null-terminated text string \a string to the CBOR stream + * provided by \a encoder. CBOR requires that \a string be valid UTF-8, but + * TinyCBOR makes no verification of correctness. The terminating null is not + * included in the stream. + * + * \sa cbor_encode_text_string, cbor_encode_byte_string + */ + +/** + * Appends the text string \a string of length \a length to the CBOR stream + * provided by \a encoder. CBOR requires that \a string be valid UTF-8, but + * TinyCBOR makes no verification of correctness. + * + * \sa CborError cbor_encode_text_stringz, cbor_encode_byte_string + */ +CborError cbor_encode_byte_string(CborEncoder *encoder, const uint8_t *string, size_t length) +{ + return encode_string(encoder, length, ByteStringType << MajorTypeShift, string); +} + +/** + * Appends the byte string \a string of length \a length to the CBOR stream + * provided by \a encoder. CBOR byte strings are arbitrary raw data. + * + * \sa cbor_encode_text_stringz, cbor_encode_text_string + */ +CborError cbor_encode_text_string(CborEncoder *encoder, const char *string, size_t length) +{ + return encode_string(encoder, length, TextStringType << MajorTypeShift, string); +} + +#ifdef __GNUC__ +__attribute__((noinline)) +#endif +static CborError create_container(CborEncoder *encoder, CborEncoder *container, size_t length, uint8_t shiftedMajorType) +{ + CborError err; + container->data.ptr = encoder->data.ptr; + container->end = encoder->end; + saturated_decrement(encoder); + container->remaining = length + 1; /* overflow ok on CborIndefiniteLength */ + + cbor_static_assert(((MapType << MajorTypeShift) & CborIteratorFlag_ContainerIsMap) == CborIteratorFlag_ContainerIsMap); + cbor_static_assert(((ArrayType << MajorTypeShift) & CborIteratorFlag_ContainerIsMap) == 0); + container->flags = shiftedMajorType & CborIteratorFlag_ContainerIsMap; + + if (length == CborIndefiniteLength) { + container->flags |= CborIteratorFlag_UnknownLength; + err = append_byte_to_buffer(container, shiftedMajorType + IndefiniteLength); + } else { + if (shiftedMajorType & CborIteratorFlag_ContainerIsMap) + container->remaining += length; + err = encode_number_no_update(container, length, shiftedMajorType); + } + return err; +} + +/** + * Creates a CBOR array in the CBOR stream provided by \a encoder and + * initializes \a arrayEncoder so that items can be added to the array using + * the CborEncoder functions. The array must be terminated by calling either + * cbor_encoder_close_container() or cbor_encoder_close_container_checked() + * with the same \a encoder and \a arrayEncoder parameters. + * + * The number of items inserted into the array must be exactly \a length items, + * otherwise the stream is invalid. If the number of items is not known when + * creating the array, the constant \ref CborIndefiniteLength may be passed as + * length instead. + * + * \sa cbor_encoder_create_map + */ +CborError cbor_encoder_create_array(CborEncoder *encoder, CborEncoder *arrayEncoder, size_t length) +{ + return create_container(encoder, arrayEncoder, length, ArrayType << MajorTypeShift); +} + +/** + * Creates a CBOR map in the CBOR stream provided by \a encoder and + * initializes \a mapEncoder so that items can be added to the map using + * the CborEncoder functions. The map must be terminated by calling either + * cbor_encoder_close_container() or cbor_encoder_close_container_checked() + * with the same \a encoder and \a mapEncoder parameters. + * + * The number of pair of items inserted into the map must be exactly \a length + * items, otherwise the stream is invalid. If the number is not known + * when creating the map, the constant \ref CborIndefiniteLength may be passed as + * length instead. + * + * \b{Implementation limitation:} TinyCBOR cannot encode more than SIZE_MAX/2 + * key-value pairs in the stream. If the length \a length is larger than this + * value (and is not \ref CborIndefiniteLength), this function returns error + * CborErrorDataTooLarge. + * + * \sa cbor_encoder_create_array + */ +CborError cbor_encoder_create_map(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length) +{ + if (length != CborIndefiniteLength && length > SIZE_MAX / 2) + return CborErrorDataTooLarge; + return create_container(encoder, mapEncoder, length, MapType << MajorTypeShift); +} + +/** + * Closes the CBOR container (array or map) provided by \a containerEncoder and + * updates the CBOR stream provided by \a encoder. Both parameters must be the + * same as were passed to cbor_encoder_create_array() or + * cbor_encoder_create_map(). + * + * Since version 0.5, this function verifies that the number of items (or pairs + * of items, in the case of a map) was correct. It is no longer necessary to call + * cbor_encoder_close_container_checked() instead. + * + * \sa cbor_encoder_create_array(), cbor_encoder_create_map() + */ +CborError cbor_encoder_close_container(CborEncoder *encoder, const CborEncoder *containerEncoder) +{ + if (encoder->end) + encoder->data.ptr = containerEncoder->data.ptr; + else + encoder->data.bytes_needed = containerEncoder->data.bytes_needed; + encoder->end = containerEncoder->end; + if (containerEncoder->flags & CborIteratorFlag_UnknownLength) + return append_byte_to_buffer(encoder, BreakByte); + + if (containerEncoder->remaining != 1) + return containerEncoder->remaining == 0 ? CborErrorTooManyItems : CborErrorTooFewItems; + + if (!encoder->end) + return CborErrorOutOfMemory; /* keep the state */ + return CborNoError; +} + +/** + * \fn CborError cbor_encode_boolean(CborEncoder *encoder, bool value) + * + * Appends the boolean value \a value to the CBOR stream provided by \a encoder. + */ + +/** + * \fn CborError cbor_encode_null(CborEncoder *encoder) + * + * Appends the CBOR type representing a null value to the CBOR stream provided + * by \a encoder. + * + * \sa cbor_encode_undefined() + */ + +/** + * \fn CborError cbor_encode_undefined(CborEncoder *encoder) + * + * Appends the CBOR type representing an undefined value to the CBOR stream + * provided by \a encoder. + * + * \sa cbor_encode_null() + */ + +/** + * \fn CborError cbor_encode_half_float(CborEncoder *encoder, const void *value) + * + * Appends the IEEE 754 half-precision (16-bit) floating point value pointed to + * by \a value to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_float(), cbor_encode_double() + */ + +/** + * \fn CborError cbor_encode_float(CborEncoder *encoder, float value) + * + * Appends the IEEE 754 single-precision (32-bit) floating point value \a value + * to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_double() + */ + +/** + * \fn CborError cbor_encode_double(CborEncoder *encoder, double value) + * + * Appends the IEEE 754 double-precision (64-bit) floating point value \a value + * to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_float() + */ + +/** + * \fn size_t cbor_encoder_get_buffer_size(const CborEncoder *encoder, const uint8_t *buffer) + * + * Returns the total size of the buffer starting at \a buffer after the + * encoding finished without errors. The \a encoder and \a buffer arguments + * must be the same as supplied to cbor_encoder_init(). + * + * If the encoding process had errors, the return value of this function is + * meaningless. If the only errors were CborErrorOutOfMemory, instead use + * cbor_encoder_get_extra_bytes_needed() to find out by how much to grow the + * buffer before encoding again. + * + * See \ref CborEncoding for an example of using this function. + * + * \sa cbor_encoder_init(), cbor_encoder_get_extra_bytes_needed(), CborEncoding + */ + +/** + * \fn size_t cbor_encoder_get_extra_bytes_needed(const CborEncoder *encoder) + * + * Returns how many more bytes the original buffer supplied to + * cbor_encoder_init() needs to be extended by so that no CborErrorOutOfMemory + * condition will happen for the encoding. If the buffer was big enough, this + * function returns 0. The \a encoder must be the original argument as passed + * to cbor_encoder_init(). + * + * This function is usually called after an encoding sequence ended with one or + * more CborErrorOutOfMemory errors, but no other error. If any other error + * happened, the return value of this function is meaningless. + * + * See \ref CborEncoding for an example of using this function. + * + * \sa cbor_encoder_init(), cbor_encoder_get_buffer_size(), CborEncoding + */ + +/** @} */ diff --git a/client/tinycbor/cborencoder_close_container_checked.c b/client/tinycbor/cborencoder_close_container_checked.c new file mode 100644 index 00000000..5661e4d5 --- /dev/null +++ b/client/tinycbor/cborencoder_close_container_checked.c @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" + +/** + * \addtogroup CborEncoding + * @{ + */ + +/** + * @deprecated + * + * Closes the CBOR container (array or map) provided by \a containerEncoder and + * updates the CBOR stream provided by \a encoder. Both parameters must be the + * same as were passed to cbor_encoder_create_array() or + * cbor_encoder_create_map(). + * + * Prior to version 0.5, cbor_encoder_close_container() did not check the + * number of items added. Since that version, it does and now + * cbor_encoder_close_container_checked() is no longer needed. + * + * \sa cbor_encoder_create_array(), cbor_encoder_create_map() + */ +CborError cbor_encoder_close_container_checked(CborEncoder *encoder, const CborEncoder *containerEncoder) +{ + return cbor_encoder_close_container(encoder, containerEncoder); +} + +/** @} */ diff --git a/client/tinycbor/cborerrorstrings.c b/client/tinycbor/cborerrorstrings.c new file mode 100644 index 00000000..3fe3a982 --- /dev/null +++ b/client/tinycbor/cborerrorstrings.c @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#include "cbor.h" + +#ifndef _ +# define _(msg) msg +#endif + +/** + * \enum CborError + * \ingroup CborGlobals + * The CborError enum contains the possible error values used by the CBOR encoder and decoder. + * + * TinyCBOR functions report success by returning CborNoError, or one error + * condition by returning one of the values below. One exception is the + * out-of-memory condition (CborErrorOutOfMemory), which the functions for \ref + * CborEncoding may report in bit-wise OR with other conditions. + * + * This technique allows code to determine whether the only error condition was + * a lack of buffer space, which may not be a fatal condition if the buffer can + * be resized. Additionally, the functions for \ref CborEncoding may continue + * to be used even after CborErrorOutOfMemory is returned, and instead they + * will simply calculate the extra space needed. + * + * \value CborNoError No error occurred + * \omitvalue CborUnknownError + * \value CborErrorUnknownLength Request for the length of an array, map or string whose length is not provided in the CBOR stream + * \value CborErrorAdvancePastEOF Not enough data in the stream to decode item (decoding would advance past end of stream) + * \value CborErrorIO An I/O error occurred, probably due to an out-of-memory situation + * \value CborErrorGarbageAtEnd Bytes exist past the end of the CBOR stream + * \value CborErrorUnexpectedEOF End of stream reached unexpectedly + * \value CborErrorUnexpectedBreak A CBOR break byte was found where not expected + * \value CborErrorUnknownType An unknown type (future extension to CBOR) was found in the stream + * \value CborErrorIllegalType An invalid type was found while parsing a chunked CBOR string + * \value CborErrorIllegalNumber An illegal initial byte (encoding unspecified additional information) was found + * \value CborErrorIllegalSimpleType An illegal encoding of a CBOR Simple Type of value less than 32 was found + * \omitvalue CborErrorUnknownSimpleType + * \omitvalue CborErrorUnknownTag + * \omitvalue CborErrorInappropriateTagForType + * \omitvalue CborErrorDuplicateObjectKeys + * \value CborErrorInvalidUtf8TextString Illegal UTF-8 encoding found while parsing CBOR Text String + * \value CborErrorTooManyItems Too many items were added to CBOR map or array of pre-determined length + * \value CborErrorTooFewItems Too few items were added to CBOR map or array of pre-determined length + * \value CborErrorDataTooLarge Data item size exceeds TinyCBOR's implementation limits + * \value CborErrorNestingTooDeep Data item nesting exceeds TinyCBOR's implementation limits + * \omitvalue CborErrorUnsupportedType + * \value CborErrorJsonObjectKeyIsAggregate Conversion to JSON failed because the key in a map is a CBOR map or array + * \value CborErrorJsonObjectKeyNotString Conversion to JSON failed because the key in a map is not a text string + * \value CborErrorOutOfMemory During CBOR encoding, the buffer provided is insufficient for encoding the data item; + * in other situations, TinyCBOR failed to allocate memory + * \value CborErrorInternalError An internal error occurred in TinyCBOR + */ + +/** + * \ingroup CborGlobals + * Returns the error string corresponding to the CBOR error condition \a error. + */ +const char *cbor_error_string(CborError error) +{ + switch (error) { + case CborNoError: + return ""; + + case CborUnknownError: + return _("unknown error"); + + case CborErrorOutOfMemory: + return _("out of memory/need more memory"); + + case CborErrorUnknownLength: + return _("unknown length (attempted to get the length of a map/array/string of indeterminate length"); + + case CborErrorAdvancePastEOF: + return _("attempted to advance past EOF"); + + case CborErrorIO: + return _("I/O error"); + + case CborErrorGarbageAtEnd: + return _("garbage after the end of the content"); + + case CborErrorUnexpectedEOF: + return _("unexpected end of data"); + + case CborErrorUnexpectedBreak: + return _("unexpected 'break' byte"); + + case CborErrorUnknownType: + return _("illegal byte (encodes future extension type)"); + + case CborErrorIllegalType: + return _("mismatched string type in chunked string"); + + case CborErrorIllegalNumber: + return _("illegal initial byte (encodes unspecified additional information)"); + + case CborErrorIllegalSimpleType: + return _("illegal encoding of simple type smaller than 32"); + + case CborErrorUnknownSimpleType: + return _("unknown simple type"); + + case CborErrorUnknownTag: + return _("unknown tag"); + + case CborErrorInappropriateTagForType: + return _("inappropriate tag for type"); + + case CborErrorDuplicateObjectKeys: + return _("duplicate keys in object"); + + case CborErrorInvalidUtf8TextString: + return _("invalid UTF-8 content in string"); + + case CborErrorExcludedType: + return _("excluded type found"); + + case CborErrorExcludedValue: + return _("excluded value found"); + + case CborErrorImproperValue: + case CborErrorOverlongEncoding: + return _("value encoded in non-canonical form"); + + case CborErrorMapKeyNotString: + case CborErrorJsonObjectKeyNotString: + return _("key in map is not a string"); + + case CborErrorMapNotSorted: + return _("map is not sorted"); + + case CborErrorMapKeysNotUnique: + return _("map keys are not unique"); + + case CborErrorTooManyItems: + return _("too many items added to encoder"); + + case CborErrorTooFewItems: + return _("too few items added to encoder"); + + case CborErrorDataTooLarge: + return _("internal error: data too large"); + + case CborErrorNestingTooDeep: + return _("internal error: too many nested containers found in recursive function"); + + case CborErrorUnsupportedType: + return _("unsupported type"); + + case CborErrorJsonObjectKeyIsAggregate: + return _("conversion to JSON failed: key in object is an array or map"); + + case CborErrorJsonNotImplemented: + return _("conversion to JSON failed: open_memstream unavailable"); + + case CborErrorInternalError: + return _("internal error"); + } + return cbor_error_string(CborUnknownError); +} diff --git a/client/tinycbor/cborinternal_p.h b/client/tinycbor/cborinternal_p.h new file mode 100644 index 00000000..a85a9297 --- /dev/null +++ b/client/tinycbor/cborinternal_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef CBORINTERNAL_P_H +#define CBORINTERNAL_P_H + +#include "compilersupport_p.h" + +#ifndef CBOR_NO_FLOATING_POINT +# include +# include +#else +# ifndef CBOR_NO_HALF_FLOAT_TYPE +# define CBOR_NO_HALF_FLOAT_TYPE 1 +# endif +#endif + +#ifndef CBOR_NO_HALF_FLOAT_TYPE +# ifdef __F16C__ +# include +static inline unsigned short encode_half(double val) +{ + return _cvtss_sh((float)val, 3); +} +static inline double decode_half(unsigned short half) +{ + return _cvtsh_ss(half); +} +# else +/* software implementation of float-to-fp16 conversions */ +static inline unsigned short encode_half(double val) +{ + uint64_t v; + int sign, exp, mant; + memcpy(&v, &val, sizeof(v)); + sign = v >> 63 << 15; + exp = (v >> 52) & 0x7ff; + mant = v << 12 >> 12 >> (53-11); /* keep only the 11 most significant bits of the mantissa */ + exp -= 1023; + if (exp == 1024) { + /* infinity or NaN */ + exp = 16; + mant >>= 1; + } else if (exp >= 16) { + /* overflow, as largest number */ + exp = 15; + mant = 1023; + } else if (exp >= -14) { + /* regular normal */ + } else if (exp >= -24) { + /* subnormal */ + mant |= 1024; + mant >>= -(exp + 14); + exp = -15; + } else { + /* underflow, make zero */ + return 0; + } + + /* safe cast here as bit operations above guarantee not to overflow */ + return (unsigned short)(sign | ((exp + 15) << 10) | mant); +} + +/* this function was copied & adapted from RFC 7049 Appendix D */ +static inline double decode_half(unsigned short half) +{ + int exp = (half >> 10) & 0x1f; + int mant = half & 0x3ff; + double val; + if (exp == 0) val = ldexp(mant, -24); + else if (exp != 31) val = ldexp(mant + 1024, exp - 25); + else val = mant == 0 ? INFINITY : NAN; + return half & 0x8000 ? -val : val; +} +# endif +#endif /* CBOR_NO_HALF_FLOAT_TYPE */ + +#ifndef CBOR_INTERNAL_API +# define CBOR_INTERNAL_API +#endif + +#ifndef CBOR_PARSER_MAX_RECURSIONS +# define CBOR_PARSER_MAX_RECURSIONS 1024 +#endif + +/* + * CBOR Major types + * Encoded in the high 3 bits of the descriptor byte + * See http://tools.ietf.org/html/rfc7049#section-2.1 + */ +typedef enum CborMajorTypes { + UnsignedIntegerType = 0U, + NegativeIntegerType = 1U, + ByteStringType = 2U, + TextStringType = 3U, + ArrayType = 4U, + MapType = 5U, /* a.k.a. object */ + TagType = 6U, + SimpleTypesType = 7U +} CborMajorTypes; + +/* + * CBOR simple and floating point types + * Encoded in the low 8 bits of the descriptor byte when the + * Major Type is 7. + */ +typedef enum CborSimpleTypes { + FalseValue = 20, + TrueValue = 21, + NullValue = 22, + UndefinedValue = 23, + SimpleTypeInNextByte = 24, /* not really a simple type */ + HalfPrecisionFloat = 25, /* ditto */ + SinglePrecisionFloat = 26, /* ditto */ + DoublePrecisionFloat = 27, /* ditto */ + Break = 31 +} CborSimpleTypes; + +enum { + SmallValueBitLength = 5U, + SmallValueMask = (1U << SmallValueBitLength) - 1, /* 31 */ + Value8Bit = 24U, + Value16Bit = 25U, + Value32Bit = 26U, + Value64Bit = 27U, + IndefiniteLength = 31U, + + MajorTypeShift = SmallValueBitLength, + MajorTypeMask = (int) (~0U << MajorTypeShift), + + BreakByte = (unsigned)Break | (SimpleTypesType << MajorTypeShift) +}; + +CBOR_INTERNAL_API CborError CBOR_INTERNAL_API_CC _cbor_value_extract_number(const uint8_t **ptr, const uint8_t *end, uint64_t *len); +CBOR_INTERNAL_API CborError CBOR_INTERNAL_API_CC _cbor_value_prepare_string_iteration(CborValue *it); +CBOR_INTERNAL_API CborError CBOR_INTERNAL_API_CC _cbor_value_get_string_chunk(const CborValue *value, const void **bufferptr, + size_t *len, CborValue *next); + + +#endif /* CBORINTERNAL_P_H */ diff --git a/client/tinycbor/cborjson.h b/client/tinycbor/cborjson.h new file mode 100644 index 00000000..8ff27b92 --- /dev/null +++ b/client/tinycbor/cborjson.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef CBORJSON_H +#define CBORJSON_H + +#include "cbor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Conversion to JSON */ +enum CborToJsonFlags +{ + CborConvertAddMetadata = 1, + CborConvertTagsToObjects = 2, + CborConvertIgnoreTags = 0, + + CborConvertObeyByteStringTags = 0, + CborConvertByteStringsToBase64Url = 4, + + CborConvertRequireMapStringKeys = 0, + CborConvertStringifyMapKeys = 8, + + CborConvertDefaultFlags = 0 +}; + +CBOR_API CborError cbor_value_to_json_advance(FILE *out, CborValue *value, int flags); +CBOR_INLINE_API CborError cbor_value_to_json(FILE *out, const CborValue *value, int flags) +{ + CborValue copy = *value; + return cbor_value_to_json_advance(out, ©, flags); +} + +#ifdef __cplusplus +} +#endif + +#endif /* CBORJSON_H */ + diff --git a/client/tinycbor/cborparser.c b/client/tinycbor/cborparser.c new file mode 100644 index 00000000..45de2226 --- /dev/null +++ b/client/tinycbor/cborparser.c @@ -0,0 +1,1430 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef _BSD_SOURCE +#define _BSD_SOURCE 1 +#endif +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE 1 +#endif +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" + +#include + +/** + * \defgroup CborParsing Parsing CBOR streams + * \brief Group of functions used to parse CBOR streams. + * + * TinyCBOR provides functions for pull-based stream parsing of a CBOR-encoded + * payload. The main data type for the parsing is a CborValue, which behaves + * like an iterator and can be used to extract the encoded data. It is first + * initialized with a call to cbor_parser_init() and is usually used to extract + * exactly one item, most often an array or map. + * + * Nested CborValue objects can be parsed using cbor_value_enter_container(). + * Each call to cbor_value_enter_container() must be matched by a call to + * cbor_value_leave_container(), with the exact same parameters. + * + * The example below initializes a CborParser object, begins the parsing with a + * CborValue and decodes a single integer: + * + * \code + * int extract_int(const uint8_t *buffer, size_t len) + * { + * CborParser parser; + * CborValue value; + * int result; + * cbor_parser_init(buffer, len, 0, &parser, &value); + * cbor_value_get_int(&value, &result); + * return result; + * } + * \endcode + * + * The code above does no error checking, which means it assumes the data comes + * from a source trusted to send one properly-encoded integer. The following + * example does the exact same operation, but includes error checking and + * returns 0 on parsing failure: + * + * \code + * int extract_int(const uint8_t *buffer, size_t len) + * { + * CborParser parser; + * CborValue value; + * int result; + * if (cbor_parser_init(buffer, len, 0, &parser, &value) != CborNoError) + * return 0; + * if (!cbor_value_is_integer(&value) || + * cbor_value_get_int(&value, &result) != CborNoError) + * return 0; + * return result; + * } + * \endcode + * + * Note, in the example above, that one can't distinguish a parsing failure + * from an encoded value of zero. Reporting a parsing error is left as an + * exercise to the reader. + * + * The code above does not execute a range-check either: it is possible that + * the value decoded from the CBOR stream encodes a number larger than what can + * be represented in a variable of type \c{int}. If detecting that case is + * important, the code should call cbor_value_get_int_checked() instead. + * + *

Memory and parsing constraints

+ * + * TinyCBOR is designed to run with little memory and with minimal overhead. + * Except where otherwise noted, the parser functions always run on constant + * time (O(1)), do not recurse and never allocate memory (thus, stack usage is + * bounded and is O(1)). + * + *

Error handling and preconditions

+ * + * All functions operating on a CborValue return a CborError condition, with + * CborNoError standing for the normal situation in which no parsing error + * occurred. All functions may return parsing errors in case the stream cannot + * be decoded properly, be it due to corrupted data or due to reaching the end + * of the input buffer. + * + * Error conditions must not be ignored. All decoder functions have undefined + * behavior if called after an error has been reported, and may crash. + * + * Some functions are also documented to have preconditions, like + * cbor_value_get_int() requiring that the input be an integral value. + * Violation of preconditions also results in undefined behavior and the + * program may crash. + */ + +/** + * \addtogroup CborParsing + * @{ + */ + +/** + * \struct CborValue + * + * This type contains one value parsed from the CBOR stream. Each CborValue + * behaves as an iterator in a StAX-style parser. + * + * \if privatedocs + * Implementation details: the CborValue contains these fields: + * \list + * \li ptr: pointer to the actual data + * \li flags: flags from the decoder + * \li extra: partially decoded integer value (0, 1 or 2 bytes) + * \li remaining: remaining items in this collection after this item or UINT32_MAX if length is unknown + * \endlist + * \endif + */ + +static inline uint16_t get16(const uint8_t *ptr) +{ + uint16_t result; + memcpy(&result, ptr, sizeof(result)); + return cbor_ntohs(result); +} + +static inline uint32_t get32(const uint8_t *ptr) +{ + uint32_t result; + memcpy(&result, ptr, sizeof(result)); + return cbor_ntohl(result); +} + +static inline uint64_t get64(const uint8_t *ptr) +{ + uint64_t result; + memcpy(&result, ptr, sizeof(result)); + return cbor_ntohll(result); +} + +CborError CBOR_INTERNAL_API_CC _cbor_value_extract_number(const uint8_t **ptr, const uint8_t *end, uint64_t *len) +{ + size_t bytesNeeded; + uint8_t additional_information = **ptr & SmallValueMask; + ++*ptr; + if (additional_information < Value8Bit) { + *len = additional_information; + return CborNoError; + } + if (unlikely(additional_information > Value64Bit)) + return CborErrorIllegalNumber; + + bytesNeeded = (size_t)(1 << (additional_information - Value8Bit)); + if (unlikely(bytesNeeded > (size_t)(end - *ptr))) { + return CborErrorUnexpectedEOF; + } else if (bytesNeeded == 1) { + *len = (uint8_t)(*ptr)[0]; + } else if (bytesNeeded == 2) { + *len = get16(*ptr); + } else if (bytesNeeded == 4) { + *len = get32(*ptr); + } else { + *len = get64(*ptr); + } + *ptr += bytesNeeded; + return CborNoError; +} + +static CborError extract_length(const CborParser *parser, const uint8_t **ptr, size_t *len) +{ + uint64_t v; + CborError err = _cbor_value_extract_number(ptr, parser->end, &v); + if (err) { + *len = 0; + return err; + } + + *len = (size_t)v; + if (v != *len) + return CborErrorDataTooLarge; + return CborNoError; +} + +static bool is_fixed_type(uint8_t type) +{ + return type != CborTextStringType && type != CborByteStringType && type != CborArrayType && + type != CborMapType; +} + +static CborError preparse_value(CborValue *it) +{ + const CborParser *parser = it->parser; + it->type = CborInvalidType; + + /* are we at the end? */ + if (it->ptr == parser->end) + return CborErrorUnexpectedEOF; + + uint8_t descriptor = *it->ptr; + uint8_t type = descriptor & MajorTypeMask; + it->type = type; + it->flags = 0; + it->extra = (descriptor &= SmallValueMask); + + if (descriptor > Value64Bit) { + if (unlikely(descriptor != IndefiniteLength)) + return type == CborSimpleType ? CborErrorUnknownType : CborErrorIllegalNumber; + if (likely(!is_fixed_type(type))) { + /* special case */ + it->flags |= CborIteratorFlag_UnknownLength; + it->type = type; + return CborNoError; + } + return type == CborSimpleType ? CborErrorUnexpectedBreak : CborErrorIllegalNumber; + } + + size_t bytesNeeded = descriptor < Value8Bit ? 0 : (1 << (descriptor - Value8Bit)); + if (bytesNeeded + 1 > (size_t)(parser->end - it->ptr)) + return CborErrorUnexpectedEOF; + + uint8_t majortype = type >> MajorTypeShift; + if (majortype == NegativeIntegerType) { + it->flags |= CborIteratorFlag_NegativeInteger; + it->type = CborIntegerType; + } else if (majortype == SimpleTypesType) { + switch (descriptor) { + case FalseValue: + it->extra = false; + it->type = CborBooleanType; + break; + + case SinglePrecisionFloat: + case DoublePrecisionFloat: + it->flags |= CborIteratorFlag_IntegerValueTooLarge; + /* fall through */ + case TrueValue: + case NullValue: + case UndefinedValue: + case HalfPrecisionFloat: + it->type = *it->ptr; + break; + + case SimpleTypeInNextByte: + it->extra = (uint8_t)it->ptr[1]; +#ifndef CBOR_PARSER_NO_STRICT_CHECKS + if (unlikely(it->extra < 32)) { + it->type = CborInvalidType; + return CborErrorIllegalSimpleType; + } +#endif + break; + + case 28: + case 29: + case 30: + case Break: + cbor_assert(false); /* these conditions can't be reached */ + return CborErrorUnexpectedBreak; + } + return CborNoError; + } + + /* try to decode up to 16 bits */ + if (descriptor < Value8Bit) + return CborNoError; + + if (descriptor == Value8Bit) + it->extra = (uint8_t)it->ptr[1]; + else if (descriptor == Value16Bit) + it->extra = get16(it->ptr + 1); + else + it->flags |= CborIteratorFlag_IntegerValueTooLarge; /* Value32Bit or Value64Bit */ + return CborNoError; +} + +static CborError preparse_next_value_nodecrement(CborValue *it) +{ + if (it->remaining == UINT32_MAX && it->ptr != it->parser->end && *it->ptr == (uint8_t)BreakByte) { + /* end of map or array */ + ++it->ptr; + it->type = CborInvalidType; + it->remaining = 0; + return CborNoError; + } + + return preparse_value(it); +} + +static CborError preparse_next_value(CborValue *it) +{ + if (it->remaining != UINT32_MAX) { + /* don't decrement the item count if the current item is tag: they don't count */ + if (it->type != CborTagType && --it->remaining == 0) { + it->type = CborInvalidType; + return CborNoError; + } + } + return preparse_next_value_nodecrement(it); +} + +static CborError advance_internal(CborValue *it) +{ + uint64_t length; + CborError err = _cbor_value_extract_number(&it->ptr, it->parser->end, &length); + cbor_assert(err == CborNoError); + + if (it->type == CborByteStringType || it->type == CborTextStringType) { + cbor_assert(length == (size_t)length); + cbor_assert((it->flags & CborIteratorFlag_UnknownLength) == 0); + it->ptr += length; + } + + return preparse_next_value(it); +} + +/** \internal + * + * Decodes the CBOR integer value when it is larger than the 16 bits available + * in value->extra. This function requires that value->flags have the + * CborIteratorFlag_IntegerValueTooLarge flag set. + * + * This function is also used to extract single- and double-precision floating + * point values (SinglePrecisionFloat == Value32Bit and DoublePrecisionFloat == + * Value64Bit). + */ +uint64_t _cbor_value_decode_int64_internal(const CborValue *value) +{ + cbor_assert(value->flags & CborIteratorFlag_IntegerValueTooLarge || + value->type == CborFloatType || value->type == CborDoubleType); + + /* since the additional information can only be Value32Bit or Value64Bit, + * we just need to test for the one bit those two options differ */ + cbor_assert((*value->ptr & SmallValueMask) == Value32Bit || (*value->ptr & SmallValueMask) == Value64Bit); + if ((*value->ptr & 1) == (Value32Bit & 1)) + return get32(value->ptr + 1); + + cbor_assert((*value->ptr & SmallValueMask) == Value64Bit); + return get64(value->ptr + 1); +} + +/** + * Initializes the CBOR parser for parsing \a size bytes beginning at \a + * buffer. Parsing will use flags set in \a flags. The iterator to the first + * element is returned in \a it. + * + * The \a parser structure needs to remain valid throughout the decoding + * process. It is not thread-safe to share one CborParser among multiple + * threads iterating at the same time, but the object can be copied so multiple + * threads can iterate. + */ +CborError cbor_parser_init(const uint8_t *buffer, size_t size, uint32_t flags, CborParser *parser, CborValue *it) +{ + memset(parser, 0, sizeof(*parser)); + parser->end = buffer + size; + parser->flags = flags; + it->parser = parser; + it->ptr = buffer; + it->remaining = 1; /* there's one type altogether, usually an array or map */ + return preparse_value(it); +} + +/** + * \fn bool cbor_value_at_end(const CborValue *it) + * + * Returns true if \a it has reached the end of the iteration, usually when + * advancing after the last item in an array or map. + * + * In the case of the outermost CborValue object, this function returns true + * after decoding a single element. A pointer to the first byte of the + * remaining data (if any) can be obtained with cbor_value_get_next_byte(). + * + * \sa cbor_value_advance(), cbor_value_is_valid(), cbor_value_get_next_byte() + */ + +/** + * \fn const uint8_t *cbor_value_get_next_byte(const CborValue *it) + * + * Returns a pointer to the next byte that would be decoded if this CborValue + * object were advanced. + * + * This function is useful if cbor_value_at_end() returns true for the + * outermost CborValue: the pointer returned is the first byte of the data + * remaining in the buffer, if any. Code can decide whether to begin decoding a + * new CBOR data stream from this point, or parse some other data appended to + * the same buffer. + * + * This function may be used even after a parsing error. If that occurred, + * then this function returns a pointer to where the parsing error occurred. + * Note that the error recovery is not precise and the pointer may not indicate + * the exact byte containing bad data. + * + * \sa cbor_value_at_end() + */ + +/** + * \fn bool cbor_value_is_valid(const CborValue *it) + * + * Returns true if the iterator \a it contains a valid value. Invalid iterators + * happen when iteration reaches the end of a container (see \ref + * cbor_value_at_end()) or when a search function resulted in no matches. + * + * \sa cbor_value_advance(), cbor_value_at_end(), cbor_value_get_type() + */ + +/** + * Performs a basic validation of the CBOR stream pointed by \a it and returns + * the error it found. If no error was found, it returns CborNoError and the + * application can iterate over the items with certainty that no other errors + * will appear during parsing. + * + * A basic validation checks for: + * \list + * \li absence of undefined additional information bytes; + * \li well-formedness of all numbers, lengths, and simple values; + * \li string contents match reported sizes; + * \li arrays and maps contain the number of elements they are reported to have; + * \endlist + * + * For further checks, see cbor_value_validate(). + * + * This function has the same timing and memory requirements as + * cbor_value_advance(). + * + * \sa cbor_value_validate(), cbor_value_advance() + */ +CborError cbor_value_validate_basic(const CborValue *it) +{ + CborValue value = *it; + return cbor_value_advance(&value); +} + +/** + * Advances the CBOR value \a it by one fixed-size position. Fixed-size types + * are: integers, tags, simple types (including boolean, null and undefined + * values) and floating point types. + * + * If the type is not of fixed size, this function has undefined behavior. Code + * must be sure that the current type is one of the fixed-size types before + * calling this function. This function is provided because it can guarantee + * that it runs in constant time (O(1)). + * + * If the caller is not able to determine whether the type is fixed or not, code + * can use the cbor_value_advance() function instead. + * + * \sa cbor_value_at_end(), cbor_value_advance(), cbor_value_enter_container(), cbor_value_leave_container() + */ +CborError cbor_value_advance_fixed(CborValue *it) +{ + cbor_assert(it->type != CborInvalidType); + cbor_assert(is_fixed_type(it->type)); + if (!it->remaining) + return CborErrorAdvancePastEOF; + return advance_internal(it); +} + +static CborError advance_recursive(CborValue *it, int nestingLevel) +{ + CborError err; + CborValue recursed; + + if (is_fixed_type(it->type)) + return advance_internal(it); + + if (!cbor_value_is_container(it)) { + size_t len = SIZE_MAX; + return _cbor_value_copy_string(it, NULL, &len, it); + } + + /* map or array */ + if (nestingLevel == 0) + return CborErrorNestingTooDeep; + + err = cbor_value_enter_container(it, &recursed); + if (err) + return err; + while (!cbor_value_at_end(&recursed)) { + err = advance_recursive(&recursed, nestingLevel - 1); + if (err) + return err; + } + return cbor_value_leave_container(it, &recursed); +} + + +/** + * Advances the CBOR value \a it by one element, skipping over containers. + * Unlike cbor_value_advance_fixed(), this function can be called on a CBOR + * value of any type. However, if the type is a container (map or array) or a + * string with a chunked payload, this function will not run in constant time + * and will recurse into itself (it will run on O(n) time for the number of + * elements or chunks and will use O(n) memory for the number of nested + * containers). + * + * The number of recursions can be limited at compile time to avoid stack + * exhaustion in constrained systems. + * + * \sa cbor_value_at_end(), cbor_value_advance_fixed(), cbor_value_enter_container(), cbor_value_leave_container() + */ +CborError cbor_value_advance(CborValue *it) +{ + cbor_assert(it->type != CborInvalidType); + if (!it->remaining) + return CborErrorAdvancePastEOF; + return advance_recursive(it, CBOR_PARSER_MAX_RECURSIONS); +} + +/** + * \fn bool cbor_value_is_tag(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR tag. + * + * \sa cbor_value_get_tag(), cbor_value_skip_tag() + */ + +/** + * \fn CborError cbor_value_get_tag(const CborValue *value, CborTag *result) + * + * Retrieves the CBOR tag value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to a CBOR tag value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_tag is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_tag() + */ + +/** + * Advances the CBOR value \a it until it no longer points to a tag. If \a it is + * already not pointing to a tag, then this function returns it unchanged. + * + * This function does not run in constant time: it will run on O(n) for n being + * the number of tags. It does use constant memory (O(1) memory requirements). + * + * \sa cbor_value_advance_fixed(), cbor_value_advance() + */ +CborError cbor_value_skip_tag(CborValue *it) +{ + while (cbor_value_is_tag(it)) { + CborError err = cbor_value_advance_fixed(it); + if (err) + return err; + } + return CborNoError; +} + +/** + * \fn bool cbor_value_is_container(const CborValue *it) + * + * Returns true if the \a it value is a container and requires recursion in + * order to decode (maps and arrays), false otherwise. + */ + +/** + * Creates a CborValue iterator pointing to the first element of the container + * represented by \a it and saves it in \a recursed. The \a it container object + * needs to be kept and passed again to cbor_value_leave_container() in order + * to continue iterating past this container. + * + * The \a it CborValue iterator must point to a container. + * + * \sa cbor_value_is_container(), cbor_value_leave_container(), cbor_value_advance() + */ +CborError cbor_value_enter_container(const CborValue *it, CborValue *recursed) +{ + cbor_assert(cbor_value_is_container(it)); + *recursed = *it; + + if (it->flags & CborIteratorFlag_UnknownLength) { + recursed->remaining = UINT32_MAX; + ++recursed->ptr; + } else { + uint64_t len; + CborError err = _cbor_value_extract_number(&recursed->ptr, recursed->parser->end, &len); + cbor_assert(err == CborNoError); + + recursed->remaining = (uint32_t)len; + if (recursed->remaining != len || len == UINT32_MAX) { + /* back track the pointer to indicate where the error occurred */ + recursed->ptr = it->ptr; + return CborErrorDataTooLarge; + } + if (recursed->type == CborMapType) { + /* maps have keys and values, so we need to multiply by 2 */ + if (recursed->remaining > UINT32_MAX / 2) { + /* back track the pointer to indicate where the error occurred */ + recursed->ptr = it->ptr; + return CborErrorDataTooLarge; + } + recursed->remaining *= 2; + } + if (len == 0) { + /* the case of the empty container */ + recursed->type = CborInvalidType; + return CborNoError; + } + } + return preparse_next_value_nodecrement(recursed); +} + +/** + * Updates \a it to point to the next element after the container. The \a + * recursed object needs to point to the element obtained either by advancing + * the last element of the container (via cbor_value_advance(), + * cbor_value_advance_fixed(), a nested cbor_value_leave_container(), or the \c + * next pointer from cbor_value_copy_string() or cbor_value_dup_string()). + * + * The \a it and \a recursed parameters must be the exact same as passed to + * cbor_value_enter_container(). + * + * \sa cbor_value_enter_container(), cbor_value_at_end() + */ +CborError cbor_value_leave_container(CborValue *it, const CborValue *recursed) +{ + cbor_assert(cbor_value_is_container(it)); + cbor_assert(recursed->type == CborInvalidType); + it->ptr = recursed->ptr; + return preparse_next_value(it); +} + + +/** + * \fn CborType cbor_value_get_type(const CborValue *value) + * + * Returns the type of the CBOR value that the iterator \a value points to. If + * \a value does not point to a valid value, this function returns \ref + * CborInvalidType. + * + * TinyCBOR also provides functions to test directly if a given CborValue object + * is of a given type, like cbor_value_is_text_string() and cbor_value_is_null(). + * + * \sa cbor_value_is_valid() + */ + +/** + * \fn bool cbor_value_is_null(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR null type. + * + * \sa cbor_value_is_valid(), cbor_value_is_undefined() + */ + +/** + * \fn bool cbor_value_is_undefined(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR undefined type. + * + * \sa cbor_value_is_valid(), cbor_value_is_null() + */ + +/** + * \fn bool cbor_value_is_boolean(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR boolean + * type (true or false). + * + * \sa cbor_value_is_valid(), cbor_value_get_boolean() + */ + +/** + * \fn CborError cbor_value_get_boolean(const CborValue *value, bool *result) + * + * Retrieves the boolean value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to a boolean value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_boolean is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_boolean() + */ + +/** + * \fn bool cbor_value_is_simple_type(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR Simple Type + * type (other than true, false, null and undefined). + * + * \sa cbor_value_is_valid(), cbor_value_get_simple_type() + */ + +/** + * \fn CborError cbor_value_get_simple_type(const CborValue *value, uint8_t *result) + * + * Retrieves the CBOR Simple Type value that \a value points to and stores it + * in \a result. If the iterator \a value does not point to a simple_type + * value, the behavior is undefined, so checking with \ref cbor_value_get_type + * or with \ref cbor_value_is_simple_type is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_simple_type() + */ + +/** + * \fn bool cbor_value_is_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR integer + * type. + * + * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_uint64, cbor_value_get_raw_integer + */ + +/** + * \fn bool cbor_value_is_unsigned_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR unsigned + * integer type (positive values or zero). + * + * \sa cbor_value_is_valid(), cbor_value_get_uint64() + */ + +/** + * \fn bool cbor_value_is_negative_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR negative + * integer type. + * + * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_raw_integer + */ + +/** + * \fn CborError cbor_value_get_int(const CborValue *value, int *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Note that this function does not do range-checking: integral values that do + * not fit in a variable of type \c{int} are silently truncated to fit. Use + * cbor_value_get_int_checked() if that is not acceptable. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * \fn CborError cbor_value_get_int64(const CborValue *value, int64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Note that this function does not do range-checking: integral values that do + * not fit in a variable of type \c{int64_t} are silently truncated to fit. Use + * cbor_value_get_int64_checked() that is not acceptable. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * \fn CborError cbor_value_get_uint64(const CborValue *value, uint64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an unsigned integer + * value, the behavior is undefined, so checking with \ref cbor_value_get_type + * or with \ref cbor_value_is_unsigned_integer is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_unsigned_integer() + */ + +/** + * \fn CborError cbor_value_get_raw_integer(const CborValue *value, uint64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * This function is provided because CBOR negative integers can assume values + * that cannot be represented with normal 64-bit integer variables. + * + * If the integer is unsigned (that is, if cbor_value_is_unsigned_integer() + * returns true), then \a result will contain the actual value. If the integer + * is negative, then \a result will contain the absolute value of that integer, + * minus one. That is, \c {actual = -result - 1}. On architectures using two's + * complement for representation of negative integers, it is equivalent to say + * that \a result will contain the bitwise negation of the actual value. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Unlike \ref cbor_value_get_int64(), this function performs a check to see if the + * stored integer fits in \a result without data loss. If the number is outside + * the valid range for the data type, this function returns the recoverable + * error CborErrorDataTooLarge. In that case, use either + * cbor_value_get_uint64() (if the number is positive) or + * cbor_value_get_raw_integer(). + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64() + */ +CborError cbor_value_get_int64_checked(const CborValue *value, int64_t *result) +{ + uint64_t v; + cbor_assert(cbor_value_is_integer(value)); + v = _cbor_value_extract_int64_helper(value); + + /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): + * "[if] the new type is signed and the value cannot be represented in it; either the + * result is implementation-defined or an implementation-defined signal is raised." + * + * The range for int64_t is -2^63 to 2^63-1 (int64_t is required to be + * two's complement, C11 7.20.1.1 paragraph 3), which in CBOR is + * represented the same way, differing only on the "sign bit" (the major + * type). + */ + + if (unlikely(v > (uint64_t)INT64_MAX)) + return CborErrorDataTooLarge; + + *result = v; + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +/** + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Unlike \ref cbor_value_get_int(), this function performs a check to see if the + * stored integer fits in \a result without data loss. If the number is outside + * the valid range for the data type, this function returns the recoverable + * error CborErrorDataTooLarge. In that case, use one of the other integer + * functions to obtain the value. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64(), + * cbor_value_get_uint64(), cbor_value_get_int64_checked(), cbor_value_get_raw_integer() + */ +CborError cbor_value_get_int_checked(const CborValue *value, int *result) +{ + uint64_t v; + cbor_assert(cbor_value_is_integer(value)); + v = _cbor_value_extract_int64_helper(value); + + /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): + * "[if] the new type is signed and the value cannot be represented in it; either the + * result is implementation-defined or an implementation-defined signal is raised." + * + * But we can convert from signed to unsigned without fault (paragraph 2). + * + * The range for int is implementation-defined and int is not guaranteed to use + * two's complement representation (although int32_t is). + */ + + if (value->flags & CborIteratorFlag_NegativeInteger) { + if (unlikely(v > (unsigned) -(INT_MIN + 1))) + return CborErrorDataTooLarge; + + *result = (int)v; + *result = -*result - 1; + } else { + if (unlikely(v > (uint64_t)INT_MAX)) + return CborErrorDataTooLarge; + + *result = (int)v; + } + return CborNoError; + +} + +/** + * \fn bool cbor_value_is_length_known(const CborValue *value) + * + * Returns true if the length of this type is known without calculation. That + * is, if the length of this CBOR string, map or array is encoded in the data + * stream, this function returns true. If the length is not encoded, it returns + * false. + * + * If the length is known, code can call cbor_value_get_string_length(), + * cbor_value_get_array_length() or cbor_value_get_map_length() to obtain the + * length. If the length is not known but is necessary, code can use the + * cbor_value_calculate_string_length() function (no equivalent function is + * provided for maps and arrays). + */ + +/** + * \fn bool cbor_value_is_text_string(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR text + * string. CBOR text strings are UTF-8 encoded and usually contain + * human-readable text. + * + * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), + * cbor_value_copy_text_string(), cbor_value_dup_text_string() + */ + +/** + * \fn bool cbor_value_is_byte_string(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR text + * string. CBOR byte strings are binary data with no specified encoding or + * format. + * + * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), + * cbor_value_copy_byte_string(), cbor_value_dup_byte_string() + */ + +/** + * \fn CborError cbor_value_get_string_length(const CborValue *value, size_t *length) + * + * Extracts the length of the byte or text string that \a value points to and + * stores it in \a result. If the iterator \a value does not point to a text + * string or a byte string, the behaviour is undefined, so checking with \ref + * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref + * cbor_value_is_byte_string is recommended. + * + * If the length of this string is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * If the length of the string is required but the length was not encoded, use + * cbor_value_calculate_string_length(), but note that that function does not + * run in constant time. + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known(), cbor_value_calculate_string_length() + */ + +/** + * Calculates the length of the byte or text string that \a value points to and + * stores it in \a len. If the iterator \a value does not point to a text + * string or a byte string, the behaviour is undefined, so checking with \ref + * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref + * cbor_value_is_byte_string is recommended. + * + * This function is different from cbor_value_get_string_length() in that it + * calculates the length even for strings sent in chunks. For that reason, this + * function may not run in constant time (it will run in O(n) time on the + * number of chunks). It does use constant memory (O(1)). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_get_string_length(), cbor_value_copy_text_string(), cbor_value_copy_byte_string(), cbor_value_is_length_known() + */ +CborError cbor_value_calculate_string_length(const CborValue *value, size_t *len) +{ + *len = SIZE_MAX; + return _cbor_value_copy_string(value, NULL, len, NULL); +} + +static inline void prepare_string_iteration(CborValue *it) +{ + if (!cbor_value_is_length_known(it)) { + /* chunked string: we're before the first chunk; + * advance to the first chunk */ + ++it->ptr; + it->flags |= CborIteratorFlag_IteratingStringChunks; + } +} + +CborError CBOR_INTERNAL_API_CC _cbor_value_prepare_string_iteration(CborValue *it) +{ + cbor_assert((it->flags & CborIteratorFlag_IteratingStringChunks) == 0); + prepare_string_iteration(it); + + /* are we at the end? */ + if (it->ptr == it->parser->end) + return CborErrorUnexpectedEOF; + return CborNoError; +} + +static CborError get_string_chunk(CborValue *it, const void **bufferptr, size_t *len) +{ + CborError err; + + /* Possible states: + * length known | iterating | meaning + * no | no | before the first chunk of a chunked string + * yes | no | at a non-chunked string + * no | yes | second or later chunk + * yes | yes | after a non-chunked string + */ + if (it->flags & CborIteratorFlag_IteratingStringChunks) { + /* already iterating */ + if (cbor_value_is_length_known(it)) { + /* if the length was known, it wasn't chunked, so finish iteration */ + goto last_chunk; + } + } else { + prepare_string_iteration(it); + } + + /* are we at the end? */ + if (it->ptr == it->parser->end) + return CborErrorUnexpectedEOF; + + if (*it->ptr == BreakByte) { + /* last chunk */ + ++it->ptr; +last_chunk: + *bufferptr = NULL; + *len = 0; + return preparse_next_value(it); + } else if ((uint8_t)(*it->ptr & MajorTypeMask) == it->type) { + err = extract_length(it->parser, &it->ptr, len); + if (err) + return err; + if (*len > (size_t)(it->parser->end - it->ptr)) + return CborErrorUnexpectedEOF; + + *bufferptr = it->ptr; + it->ptr += *len; + } else { + return CborErrorIllegalType; + } + + it->flags |= CborIteratorFlag_IteratingStringChunks; + return CborNoError; +} + +CborError CBOR_INTERNAL_API_CC +_cbor_value_get_string_chunk(const CborValue *value, const void **bufferptr, + size_t *len, CborValue *next) +{ + CborValue tmp; + if (!next) + next = &tmp; + *next = *value; + return get_string_chunk(next, bufferptr, len); +} + +/* We return uintptr_t so that we can pass memcpy directly as the iteration + * function. The choice is to optimize for memcpy, which is used in the base + * parser API (cbor_value_copy_string), while memcmp is used in convenience API + * only. */ +typedef uintptr_t (*IterateFunction)(char *, const uint8_t *, size_t); + +static uintptr_t iterate_noop(char *dest, const uint8_t *src, size_t len) +{ + (void)dest; + (void)src; + (void)len; + return true; +} + +static uintptr_t iterate_memcmp(char *s1, const uint8_t *s2, size_t len) +{ + return memcmp(s1, (const char *)s2, len) == 0; +} + +static uintptr_t iterate_memcpy(char *dest, const uint8_t *src, size_t len) +{ + return (uintptr_t)memcpy(dest, src, len); +} + +static CborError iterate_string_chunks(const CborValue *value, char *buffer, size_t *buflen, + bool *result, CborValue *next, IterateFunction func) +{ + CborError err; + CborValue tmp; + size_t total = 0; + const void *ptr; + + cbor_assert(cbor_value_is_byte_string(value) || cbor_value_is_text_string(value)); + if (!next) + next = &tmp; + *next = *value; + *result = true; + + while (1) { + size_t newTotal; + size_t chunkLen; + err = get_string_chunk(next, &ptr, &chunkLen); + if (err) + return err; + if (!ptr) + break; + + if (unlikely(add_check_overflow(total, chunkLen, &newTotal))) + return CborErrorDataTooLarge; + + if (*result && *buflen >= newTotal) + *result = !!func(buffer + total, (const uint8_t *)ptr, chunkLen); + else + *result = false; + + total = newTotal; + } + + /* is there enough room for the ending NUL byte? */ + if (*result && *buflen > total) { + uint8_t nul[] = { 0 }; + *result = !!func(buffer + total, nul, 1); + } + *buflen = total; + return CborNoError; +} + +/** + * \fn CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, size_t *buflen, CborValue *next) + * + * Copies the string pointed to by \a value into the buffer provided at \a buffer + * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not + * copy anything and will only update the \a next value. + * + * If the iterator \a value does not point to a text string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_text_string is recommended. + * + * If the provided buffer length was too small, this function returns an error + * condition of \ref CborErrorOutOfMemory. If you need to calculate the length + * of the string in order to preallocate a buffer, use + * cbor_value_calculate_string_length(). + * + * On success, this function sets the number of bytes copied to \c{*buflen}. If + * the buffer is large enough, this function will insert a null byte after the + * last copied byte, to facilitate manipulation of text strings. That byte is + * not included in the returned value of \c{*buflen}. If there was no space for + * the terminating null, no error is returned, so callers must check the value + * of *buflen after the call, before relying on the '\0'; if it has not been + * changed by the call, there is no '\0'-termination on the buffer's contents. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \note This function does not perform UTF-8 validation on the incoming text + * string. + * + * \sa cbor_value_get_text_string_chunk() cbor_value_dup_text_string(), cbor_value_copy_byte_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() + */ + +/** + * \fn CborError cbor_value_copy_byte_string(const CborValue *value, uint8_t *buffer, size_t *buflen, CborValue *next) + * + * Copies the string pointed by \a value into the buffer provided at \a buffer + * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not + * copy anything and will only update the \a next value. + * + * If the iterator \a value does not point to a byte string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_byte_string is recommended. + * + * If the provided buffer length was too small, this function returns an error + * condition of \ref CborErrorOutOfMemory. If you need to calculate the length + * of the string in order to preallocate a buffer, use + * cbor_value_calculate_string_length(). + * + * On success, this function sets the number of bytes copied to \c{*buflen}. If + * the buffer is large enough, this function will insert a null byte after the + * last copied byte, to facilitate manipulation of null-terminated strings. + * That byte is not included in the returned value of \c{*buflen}. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \sa cbor_value_get_byte_string_chunk(), cbor_value_dup_text_string(), cbor_value_copy_text_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() + */ + +CborError _cbor_value_copy_string(const CborValue *value, void *buffer, + size_t *buflen, CborValue *next) +{ + bool copied_all; + CborError err = iterate_string_chunks(value, (char*)buffer, buflen, &copied_all, next, + buffer ? iterate_memcpy : iterate_noop); + return err ? err : + copied_all ? CborNoError : CborErrorOutOfMemory; +} + +/** + * Compares the entry \a value with the string \a string and stores the result + * in \a result. If the value is different from \a string \a result will + * contain \c false. + * + * The entry at \a value may be a tagged string. If \a value is not a string or + * a tagged string, the comparison result will be false. + * + * CBOR requires text strings to be encoded in UTF-8, but this function does + * not validate either the strings in the stream or the string \a string to be + * matched. Moreover, comparison is done on strict codepoint comparison, + * without any Unicode normalization. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \sa cbor_value_skip_tag(), cbor_value_copy_text_string() + */ +CborError cbor_value_text_string_equals(const CborValue *value, const char *string, bool *result) +{ + size_t len; + CborValue copy = *value; + CborError err = cbor_value_skip_tag(©); + if (err) + return err; + if (!cbor_value_is_text_string(©)) { + *result = false; + return CborNoError; + } + + len = strlen(string); + return iterate_string_chunks(©, CONST_CAST(char *, string), &len, result, NULL, iterate_memcmp); +} + +/** + * \fn bool cbor_value_is_array(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR array. + * + * \sa cbor_value_is_valid(), cbor_value_is_map() + */ + +/** + * \fn CborError cbor_value_get_array_length(const CborValue *value, size_t *length) + * + * Extracts the length of the CBOR array that \a value points to and stores it + * in \a result. If the iterator \a value does not point to a CBOR array, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_array is recommended. + * + * If the length of this array is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known() + */ + +/** + * \fn bool cbor_value_is_map(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR map. + * + * \sa cbor_value_is_valid(), cbor_value_is_array() + */ + +/** + * \fn CborError cbor_value_get_map_length(const CborValue *value, size_t *length) + * + * Extracts the length of the CBOR map that \a value points to and stores it in + * \a result. If the iterator \a value does not point to a CBOR map, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_map is recommended. + * + * If the length of this map is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known() + */ + +/** + * Attempts to find the value in map \a map that corresponds to the text string + * entry \a string. If the iterator \a value does not point to a CBOR map, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_map is recommended. + * + * If the item is found, it is stored in \a result. If no item is found + * matching the key, then \a result will contain an element of type \ref + * CborInvalidType. Matching is performed using + * cbor_value_text_string_equals(), so tagged strings will also match. + * + * This function has a time complexity of O(n) where n is the number of + * elements in the map to be searched. In addition, this function is has O(n) + * memory requirement based on the number of nested containers (maps or arrays) + * found as elements of this map. + * + * \sa cbor_value_is_valid(), cbor_value_text_string_equals(), cbor_value_advance() + */ +CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element) +{ + CborError err; + size_t len = strlen(string); + cbor_assert(cbor_value_is_map(map)); + err = cbor_value_enter_container(map, element); + if (err) + goto error; + + while (!cbor_value_at_end(element)) { + /* find the non-tag so we can compare */ + err = cbor_value_skip_tag(element); + if (err) + goto error; + if (cbor_value_is_text_string(element)) { + bool equals; + size_t dummyLen = len; + err = iterate_string_chunks(element, CONST_CAST(char *, string), &dummyLen, + &equals, element, iterate_memcmp); + if (err) + goto error; + if (equals) + return preparse_value(element); + } else { + /* skip this key */ + err = cbor_value_advance(element); + if (err) + goto error; + } + + /* skip this value */ + err = cbor_value_skip_tag(element); + if (err) + goto error; + err = cbor_value_advance(element); + if (err) + goto error; + } + + /* not found */ + element->type = CborInvalidType; + return CborNoError; + +error: + element->type = CborInvalidType; + return err; +} + +/** + * \fn bool cbor_value_is_float(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * single-precision floating point (32-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_half_float() + */ + +/** + * \fn CborError cbor_value_get_float(const CborValue *value, float *result) + * + * Retrieves the CBOR single-precision floating point (32-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a single-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_float is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_float(), cbor_value_get_double() + */ + +/** + * \fn bool cbor_value_is_double(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * double-precision floating point (64-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_float(), cbor_value_is_half_float() + */ + +/** + * \fn CborError cbor_value_get_double(const CborValue *value, float *result) + * + * Retrieves the CBOR double-precision floating point (64-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a double-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_double is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_double(), cbor_value_get_float() + */ + +/** + * \fn bool cbor_value_is_half_float(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * single-precision floating point (16-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_float() + */ + +/** + * Retrieves the CBOR half-precision floating point (16-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a half-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_half_float is recommended. + * + * Note: since the C language does not have a standard type for half-precision + * floating point, this function takes a \c{void *} as a parameter for the + * storage area, which must be at least 16 bits wide. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_half_float(), cbor_value_get_float() + */ +CborError cbor_value_get_half_float(const CborValue *value, void *result) +{ + uint16_t v; + cbor_assert(cbor_value_is_half_float(value)); + + /* size has been computed already */ + v = get16(value->ptr + 1); + memcpy(result, &v, sizeof(v)); + return CborNoError; +} + +/** @} */ diff --git a/client/tinycbor/cborparser_dup_string.c b/client/tinycbor/cborparser_dup_string.c new file mode 100644 index 00000000..061c5ac7 --- /dev/null +++ b/client/tinycbor/cborparser_dup_string.c @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef _BSD_SOURCE +#define _BSD_SOURCE 1 +#endif +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE 1 +#endif +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "compilersupport_p.h" +#include + +/** + * \fn CborError cbor_value_dup_text_string(const CborValue *value, char **buffer, size_t *buflen, CborValue *next) + * + * Allocates memory for the string pointed by \a value and copies it into this + * buffer. The pointer to the buffer is stored in \a buffer and the number of + * bytes copied is stored in \a buflen (those variables must not be NULL). + * + * If the iterator \a value does not point to a text string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_text_string is recommended. + * + * If \c malloc returns a NULL pointer, this function will return error + * condition \ref CborErrorOutOfMemory. + * + * On success, \c{*buffer} will contain a valid pointer that must be freed by + * calling \c{free()}. This is the case even for zero-length strings. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)) in addition to the + * malloc'ed block. + * + * \note This function does not perform UTF-8 validation on the incoming text + * string. + * + * \sa cbor_value_get_text_string_chunk(), cbor_value_copy_text_string(), cbor_value_dup_byte_string() + */ + +/** + * \fn CborError cbor_value_dup_byte_string(const CborValue *value, uint8_t **buffer, size_t *buflen, CborValue *next) + * + * Allocates memory for the string pointed by \a value and copies it into this + * buffer. The pointer to the buffer is stored in \a buffer and the number of + * bytes copied is stored in \a buflen (those variables must not be NULL). + * + * If the iterator \a value does not point to a byte string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_byte_string is recommended. + * + * If \c malloc returns a NULL pointer, this function will return error + * condition \ref CborErrorOutOfMemory. + * + * On success, \c{*buffer} will contain a valid pointer that must be freed by + * calling \c{free()}. This is the case even for zero-length strings. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)) in addition to the + * malloc'ed block. + * + * \sa cbor_value_get_text_string_chunk(), cbor_value_copy_byte_string(), cbor_value_dup_text_string() + */ +CborError _cbor_value_dup_string(const CborValue *value, void **buffer, size_t *buflen, CborValue *next) +{ + CborError err; + cbor_assert(buffer); + cbor_assert(buflen); + *buflen = SIZE_MAX; + err = _cbor_value_copy_string(value, NULL, buflen, NULL); + if (err) + return err; + + ++*buflen; + *buffer = malloc(*buflen); + if (!*buffer) { + /* out of memory */ + return CborErrorOutOfMemory; + } + err = _cbor_value_copy_string(value, *buffer, buflen, next); + if (err) { + free(*buffer); + return err; + } + return CborNoError; +} diff --git a/client/tinycbor/cborpretty.c b/client/tinycbor/cborpretty.c new file mode 100644 index 00000000..b8825dee --- /dev/null +++ b/client/tinycbor/cborpretty.c @@ -0,0 +1,578 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" +#include "utf8_p.h" + +#include +#include + +/** + * \defgroup CborPretty Converting CBOR to text + * \brief Group of functions used to convert CBOR to text form. + * + * This group contains two functions that can be used to convert a \ref + * CborValue object to a text representation. This module attempts to follow + * the recommendations from RFC 7049 section 6 "Diagnostic Notation", though it + * has a few differences. They are noted below. + * + * TinyCBOR does not provide a way to convert from the text representation back + * to encoded form. To produce a text form meant to be parsed, CborToJson is + * recommended instead. + * + * Either of the functions in this section will attempt to convert exactly one + * CborValue object to text. Those functions may return any error documented + * for the functions for CborParsing. In addition, if the C standard library + * stream functions return with error, the text conversion will return with + * error CborErrorIO. + * + * These functions also perform UTF-8 validation in CBOR text strings. If they + * encounter a sequence of bytes that is not permitted in UTF-8, they will return + * CborErrorInvalidUtf8TextString. That includes encoding of surrogate points + * in UTF-8. + * + * \warning The output type produced by these functions is not guaranteed to + * remain stable. A future update of TinyCBOR may produce different output for + * the same input and parsers may be unable to handle it. + * + * \sa CborParsing, CborToJson, cbor_parser_init() + */ + +/** + * \addtogroup CborPretty + * @{ + *

Text format

+ * + * As described in RFC 7049 section 6 "Diagnostic Notation", the format is + * largely borrowed from JSON, but modified to suit CBOR's different data + * types. TinyCBOR makes further modifications to distinguish different, but + * similar values. + * + * CBOR values are currently encoded as follows: + * \par Integrals (unsigned and negative) + * Base-10 (decimal) text representation of the value + * \par Byte strings: + * "h'" followed by the Base16 (hex) representation of the binary data, followed by an ending quote (') + * \par Text strings: + * C-style escaped string in quotes, with C11/C++11 escaping of Unicode codepoints above U+007F. + * \par Tags: + * Tag value, with the tagged value in parentheses. No special encoding of the tagged value is performed. + * \par Simple types: + * "simple(nn)" where \c nn is the simple value + * \par Null: + * \c null + * \par Undefined: + * \c undefined + * \par Booleans: + * \c true or \c false + * \par Floating point: + * If NaN or infinite, the actual words \c NaN or \c infinite. + * Otherwise, the decimal representation with as many digits as necessary to ensure no loss of information. + * By default, float values are suffixed by "f" and half-float values suffixed by "f16" (doubles have no suffix). + * If the CborPrettyNumericEncodingIndicators flag is active, the values instead are encoded following the + * Section 6 recommended encoding indicators: float values are suffixed with "_2" and half-float with "_1". + * A decimal point is always present. + * \par Arrays: + * Comma-separated list of elements, enclosed in square brackets ("[" and "]"). + * \par Maps: + * Comma-separated list of key-value pairs, with the key and value separated + * by a colon (":"), enclosed in curly braces ("{" and "}"). + * + * The CborPrettyFlags enumerator contains flags to control some aspects of the + * encoding: + * \par String fragmentation + * When the CborPrettyShowStringFragments option is active, text and byte + * strings that are transmitted in fragments are shown instead inside + * parentheses ("(" and ")") with no preceding number and each fragment is + * displayed individually. If a tag precedes the string, then the output + * will contain a double set of parentheses. If the option is not active, + * the fragments are merged together and the display will not show any + * difference from a string transmitted with determinate length. + * \par Encoding indicators + * Numbers and lengths in CBOR can be encoded in multiple representations. + * If the CborPrettyIndicateOverlongNumbers option is active, numbers + * and lengths that are transmitted in a longer encoding than necessary + * will be indicated, by appending an underscore ("_") to either the + * number or the opening bracket or brace, followed by a number + * indicating the CBOR additional information: 0 for 1 byte, 1 for 2 + * bytes, 2 for 4 bytes and 3 for 8 bytes. + * If the CborPrettyIndicateIndeterminateLength option is active, maps, + * arrays and strings encoded with indeterminate length will be marked by + * an underscore after the opening bracket or brace or the string (if not + * showing fragments), without a number after it. + */ + +/** + * \enum CborPrettyFlags + * The CborPrettyFlags enum contains flags that control the conversion of CBOR to text format. + * + * \value CborPrettyNumericEncodingIndicators Use numeric encoding indicators instead of textual for float and half-float. + * \value CborPrettyTextualEncodingIndicators Use textual encoding indicators for float ("f") and half-float ("f16"). + * \value CborPrettyIndicateIndeterminateLength (default) Indicate when a map or array has indeterminate length. + * \value CborPrettyIndicateOverlongNumbers Indicate when a number or length was encoded with more bytes than needed. + * \value CborPrettyShowStringFragments If the byte or text string is transmitted in chunks, show each individually. + * \value CborPrettyMergeStringFragment Merge all chunked byte or text strings and display them in a single entry. + * \value CborPrettyDefaultFlags Default conversion flags. + */ + +#ifndef CBOR_NO_FLOATING_POINT +static inline bool convertToUint64(double v, uint64_t *absolute) +{ + double supremum; + v = fabs(v); + + /* C11 standard section 6.3.1.4 "Real floating and integer" says: + * + * 1 When a finite value of real floating type is converted to an integer + * type other than _Bool, the fractional part is discarded (i.e., the + * value is truncated toward zero). If the value of the integral part + * cannot be represented by the integer type, the behavior is undefined. + * + * So we must perform a range check that v <= UINT64_MAX, but we can't use + * UINT64_MAX + 1.0 because the standard continues: + * + * 2 When a value of integer type is converted to a real floating type, if + * the value being converted can be represented exactly in the new type, + * it is unchanged. If the value being converted is in the range of + * values that can be represented but cannot be represented exactly, the + * result is either the nearest higher or nearest lower representable + * value, chosen in an implementation-defined manner. + */ + supremum = -2.0 * INT64_MIN; /* -2 * (- 2^63) == 2^64 */ + if (v >= supremum) + return false; + + /* Now we can convert, these two conversions cannot be UB */ + *absolute = v; + return *absolute == v; +} +#endif + +static void printRecursionLimit(CborStreamFunction stream, void *out) +{ + stream(out, ""); +} + +static CborError hexDump(CborStreamFunction stream, void *out, const void *ptr, size_t n) +{ + const uint8_t *buffer = (const uint8_t *)ptr; + CborError err = CborNoError; + while (n-- && !err) + err = stream(out, "%02" PRIx8, *buffer++); + + return err; +} + +/* This function decodes buffer as UTF-8 and prints as escaped UTF-16. + * On UTF-8 decoding error, it returns CborErrorInvalidUtf8TextString */ +static CborError utf8EscapedDump(CborStreamFunction stream, void *out, const void *ptr, size_t n) +{ + const uint8_t *buffer = (const uint8_t *)ptr; + const uint8_t * const end = buffer + n; + CborError err = CborNoError; + + while (buffer < end && !err) { + uint32_t uc = get_utf8(&buffer, end); + if (uc == ~0U) + return CborErrorInvalidUtf8TextString; + + if (uc < 0x80) { + /* single-byte UTF-8 */ + unsigned char escaped = (unsigned char)uc; + if (uc < 0x7f && uc >= 0x20 && uc != '\\' && uc != '"') { + err = stream(out, "%c", (char)uc); + continue; + } + + /* print as an escape sequence */ + switch (uc) { + case '"': + case '\\': + break; + case '\b': + escaped = 'b'; + break; + case '\f': + escaped = 'f'; + break; + case '\n': + escaped = 'n'; + break; + case '\r': + escaped = 'r'; + break; + case '\t': + escaped = 't'; + break; + default: + goto print_utf16; + } + err = stream(out, "\\%c", escaped); + continue; + } + + /* now print the sequence */ + if (uc > 0xffffU) { + /* needs surrogate pairs */ + err = stream(out, "\\u%04" PRIX32 "\\u%04" PRIX32, + (uc >> 10) + 0xd7c0, /* high surrogate */ + (uc % 0x0400) + 0xdc00); + } else { +print_utf16: + /* no surrogate pair needed */ + err = stream(out, "\\u%04" PRIX32, uc); + } + } + return err; +} + +static const char *resolve_indicator(const uint8_t *ptr, const uint8_t *end, int flags) +{ + static const char indicators[8][3] = { + "_0", "_1", "_2", "_3", + "", "", "", /* these are not possible */ + "_" + }; + const char *no_indicator = indicators[5]; /* empty string */ + uint8_t additional_information; + uint8_t expected_information; + uint64_t value; + CborError err; + + if (ptr == end) + return NULL; /* CborErrorUnexpectedEOF */ + + additional_information = (*ptr & SmallValueMask); + if (additional_information < Value8Bit) + return no_indicator; + + /* determine whether to show anything */ + if ((flags & CborPrettyIndicateIndeterminateLength) && + additional_information == IndefiniteLength) + return indicators[IndefiniteLength - Value8Bit]; + if ((flags & CborPrettyIndicateOverlongNumbers) == 0) + return no_indicator; + + err = _cbor_value_extract_number(&ptr, end, &value); + if (err) + return NULL; /* CborErrorUnexpectedEOF */ + + expected_information = Value8Bit - 1; + if (value >= Value8Bit) + ++expected_information; + if (value > 0xffU) + ++expected_information; + if (value > 0xffffU) + ++expected_information; + if (value > 0xffffffffU) + ++expected_information; + return expected_information == additional_information ? + no_indicator : + indicators[additional_information - Value8Bit]; +} + +static const char *get_indicator(const CborValue *it, int flags) +{ + return resolve_indicator(it->ptr, it->parser->end, flags); +} + +static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft); +static CborError container_to_pretty(CborStreamFunction stream, void *out, CborValue *it, CborType containerType, + int flags, int recursionsLeft) +{ + const char *comma = ""; + CborError err = CborNoError; + + if (!recursionsLeft) { + printRecursionLimit(stream, out); + return err; /* do allow the dumping to continue */ + } + + while (!cbor_value_at_end(it) && !err) { + err = stream(out, "%s", comma); + comma = ", "; + + if (!err) + err = value_to_pretty(stream, out, it, flags, recursionsLeft); + + if (containerType == CborArrayType) + continue; + + /* map: that was the key, so get the value */ + if (!err) + err = stream(out, ": "); + if (!err) + err = value_to_pretty(stream, out, it, flags, recursionsLeft); + } + return err; +} + +static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft) +{ + CborError err = CborNoError; + CborType type = cbor_value_get_type(it); + switch (type) { + case CborArrayType: + case CborMapType: { + /* recursive type */ + CborValue recursed; + const char *indicator = get_indicator(it, flags); + const char *space = *indicator ? " " : indicator; + + err = stream(out, "%c%s%s", type == CborArrayType ? '[' : '{', indicator, space); + if (err) + return err; + + err = cbor_value_enter_container(it, &recursed); + if (err) { + it->ptr = recursed.ptr; + return err; /* parse error */ + } + err = container_to_pretty(stream, out, &recursed, type, flags, recursionsLeft - 1); + if (err) { + it->ptr = recursed.ptr; + return err; /* parse error */ + } + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; /* parse error */ + + return stream(out, type == CborArrayType ? "]" : "}"); + } + + case CborIntegerType: { + uint64_t val; + cbor_value_get_raw_integer(it, &val); /* can't fail */ + + if (cbor_value_is_unsigned_integer(it)) { + err = stream(out, "%" PRIu64, val); + } else { + /* CBOR stores the negative number X as -1 - X + * (that is, -1 is stored as 0, -2 as 1 and so forth) */ + if (++val) { /* unsigned overflow may happen */ + err = stream(out, "-%" PRIu64, val); + } else { + /* overflown + * 0xffff`ffff`ffff`ffff + 1 = + * 0x1`0000`0000`0000`0000 = 18446744073709551616 (2^64) */ + err = stream(out, "-18446744073709551616"); + } + } + if (!err) + err = stream(out, "%s", get_indicator(it, flags)); + break; + } + + case CborByteStringType: + case CborTextStringType: { + size_t n = 0; + const void *ptr; + bool showingFragments = (flags & CborPrettyShowStringFragments) && !cbor_value_is_length_known(it); + const char *separator = ""; + char close = '\''; + char open[3] = "h'"; + const char *indicator = NULL; + + if (type == CborTextStringType) { + close = open[0] = '"'; + open[1] = '\0'; + } + + if (showingFragments) { + err = stream(out, "(_ "); + if (!err) + err = _cbor_value_prepare_string_iteration(it); + } else { + err = stream(out, "%s", open); + } + + while (!err) { + if (showingFragments || indicator == NULL) { + /* any iteration, except the second for a non-chunked string */ + indicator = resolve_indicator(it->ptr, it->parser->end, flags); + } + + err = _cbor_value_get_string_chunk(it, &ptr, &n, it); + if (!ptr) + break; + + if (!err && showingFragments) + err = stream(out, "%s%s", separator, open); + if (!err) + err = (type == CborByteStringType ? + hexDump(stream, out, ptr, n) : + utf8EscapedDump(stream, out, ptr, n)); + if (!err && showingFragments) { + err = stream(out, "%c%s", close, indicator); + separator = ", "; + } + } + + if (!err) { + if (showingFragments) + err = stream(out, ")"); + else + err = stream(out, "%c%s", close, indicator); + } + return err; + } + + case CborTagType: { + CborTag tag; + cbor_value_get_tag(it, &tag); /* can't fail */ + err = stream(out, "%" PRIu64 "%s(", tag, get_indicator(it, flags)); + if (!err) + err = cbor_value_advance_fixed(it); + if (!err && recursionsLeft) + err = value_to_pretty(stream, out, it, flags, recursionsLeft - 1); + else if (!err) + printRecursionLimit(stream, out); + if (!err) + err = stream(out, ")"); + return err; + } + + case CborSimpleType: { + /* simple types can't fail and can't have overlong encoding */ + uint8_t simple_type; + cbor_value_get_simple_type(it, &simple_type); + err = stream(out, "simple(%" PRIu8 ")", simple_type); + break; + } + + case CborNullType: + err = stream(out, "null"); + break; + + case CborUndefinedType: + err = stream(out, "undefined"); + break; + + case CborBooleanType: { + bool val; + cbor_value_get_boolean(it, &val); /* can't fail */ + err = stream(out, val ? "true" : "false"); + break; + } + +#ifndef CBOR_NO_FLOATING_POINT + case CborDoubleType: { + const char *suffix; + double val; + int r; + uint64_t ival; + + if (false) { + float f; + case CborFloatType: + cbor_value_get_float(it, &f); + val = f; + suffix = flags & CborPrettyNumericEncodingIndicators ? "_2" : "f"; + } else if (false) { + uint16_t f16; + case CborHalfFloatType: +#ifndef CBOR_NO_HALF_FLOAT_TYPE + cbor_value_get_half_float(it, &f16); + val = decode_half(f16); + suffix = flags & CborPrettyNumericEncodingIndicators ? "_1" : "f16"; +#else + (void)f16; + err = CborErrorUnsupportedType; + break; +#endif + } else { + cbor_value_get_double(it, &val); + suffix = ""; + } + + if ((flags & CborPrettyNumericEncodingIndicators) == 0) { + r = fpclassify(val); + if (r == FP_NAN || r == FP_INFINITE) + suffix = ""; + } + + if (convertToUint64(val, &ival)) { + /* this double value fits in a 64-bit integer, so show it as such + * (followed by a floating point suffix, to disambiguate) */ + err = stream(out, "%s%" PRIu64 ".%s", val < 0 ? "-" : "", ival, suffix); + } else { + /* this number is definitely not a 64-bit integer */ + err = stream(out, "%." DBL_DECIMAL_DIG_STR "g%s", val, suffix); + } + break; + } +#else + case CborDoubleType: + case CborFloatType: + case CborHalfFloatType: + err = CborErrorUnsupportedType; + break; +#endif /* !CBOR_NO_FLOATING_POINT */ + + case CborInvalidType: + err = stream(out, "invalid"); + if (err) + return err; + return CborErrorUnknownType; + } + + if (!err) + err = cbor_value_advance_fixed(it); + return err; +} + +/** + * Converts the current CBOR type pointed by \a value to its textual + * representation and writes it to the stream by calling the \a streamFunction. + * If an error occurs, this function returns an error code similar to + * \ref CborParsing. + * + * The textual representation can be controlled by the \a flags parameter (see + * \ref CborPrettyFlags for more information). + * + * If no error ocurred, this function advances \a value to the next element. + * Often, concatenating the text representation of multiple elements can be + * done by appending a comma to the output stream in between calls to this + * function. + * + * The \a streamFunction function will be called with the \a token value as the + * first parameter and a printf-style format string as the second, with a variable + * number of further parameters. + * + * \sa cbor_value_to_pretty(), cbor_value_to_json_advance() + */ +CborError cbor_value_to_pretty_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags) +{ + return value_to_pretty(streamFunction, token, value, flags, CBOR_PARSER_MAX_RECURSIONS); +} + +/** @} */ diff --git a/client/tinycbor/cborpretty_stdio.c b/client/tinycbor/cborpretty_stdio.c new file mode 100644 index 00000000..20131850 --- /dev/null +++ b/client/tinycbor/cborpretty_stdio.c @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#include "cbor.h" +#include +#include + +static CborError cbor_fprintf(void *out, const char *fmt, ...) +{ + int n; + + va_list list; + va_start(list, fmt); + n = vfprintf((FILE *)out, fmt, list); + va_end(list); + + return n < 0 ? CborErrorIO : CborNoError; +} + +/** + * \fn CborError cbor_value_to_pretty(FILE *out, const CborValue *value) + * + * Converts the current CBOR type pointed to by \a value to its textual + * representation and writes it to the \a out stream. If an error occurs, this + * function returns an error code similar to CborParsing. + * + * \sa cbor_value_to_pretty_advance(), cbor_value_to_json_advance() + */ + +/** + * Converts the current CBOR type pointed to by \a value to its textual + * representation and writes it to the \a out stream. If an error occurs, this + * function returns an error code similar to CborParsing. + * + * If no error ocurred, this function advances \a value to the next element. + * Often, concatenating the text representation of multiple elements can be + * done by appending a comma to the output stream in between calls to this + * function. + * + * \sa cbor_value_to_pretty(), cbor_value_to_pretty_stream(), cbor_value_to_json_advance() + */ +CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value) +{ + return cbor_value_to_pretty_stream(cbor_fprintf, out, value, CborPrettyDefaultFlags); +} + +/** + * Converts the current CBOR type pointed to by \a value to its textual + * representation and writes it to the \a out stream. If an error occurs, this + * function returns an error code similar to CborParsing. + * + * The textual representation can be controlled by the \a flags parameter (see + * CborPrettyFlags for more information). + * + * If no error ocurred, this function advances \a value to the next element. + * Often, concatenating the text representation of multiple elements can be + * done by appending a comma to the output stream in between calls to this + * function. + * + * \sa cbor_value_to_pretty_stream(), cbor_value_to_pretty(), cbor_value_to_json_advance() + */ +CborError cbor_value_to_pretty_advance_flags(FILE *out, CborValue *value, int flags) +{ + return cbor_value_to_pretty_stream(cbor_fprintf, out, value, flags); +} + diff --git a/client/tinycbor/cbortojson.c b/client/tinycbor/cbortojson.c new file mode 100644 index 00000000..5a1a2e5d --- /dev/null +++ b/client/tinycbor/cbortojson.c @@ -0,0 +1,699 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#define _GNU_SOURCE 1 +#define _POSIX_C_SOURCE 200809L +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborjson.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" + +#include +#include +#include +#include + +/** + * \defgroup CborToJson Converting CBOR to JSON + * \brief Group of functions used to convert CBOR to JSON. + * + * This group contains two functions that can be used to convert a \ref + * CborValue object to an equivalent JSON representation. This module attempts + * to follow the recommendations from RFC 7049 section 4.1 "Converting from + * CBOR to JSON", though it has a few differences. They are noted below. + * + * These functions produce a "minified" JSON output, with no spacing, + * indentation or line breaks. If those are necessary, they need to be applied + * in a post-processing phase. + * + * Note that JSON cannot support all CBOR types with fidelity, so the + * conversion is usually lossy. For that reason, TinyCBOR supports adding a set + * of metadata JSON values that can be used by a JSON-to-CBOR converter to + * restore the original data types. + * + * The TinyCBOR library does not provide a way to convert from JSON + * representation back to encoded form. However, it provides a tool called + * \c json2cbor which can be used for that purpose. That tool supports the + * metadata format that these functions may produce. + * + * Either of the functions in this section will attempt to convert exactly one + * CborValue object to JSON. Those functions may return any error documented + * for the functions for CborParsing. In addition, if the C standard library + * stream functions return with error, the text conversion will return with + * error CborErrorIO. + * + * These functions also perform UTF-8 validation in CBOR text strings. If they + * encounter a sequence of bytes that is not permitted in UTF-8, they will return + * CborErrorInvalidUtf8TextString. That includes encoding of surrogate points + * in UTF-8. + * + * \warning The metadata produced by these functions is not guaranteed to + * remain stable. A future update of TinyCBOR may produce different output for + * the same input and parsers may be unable to handle it. + * + * \sa CborParsing, CborPretty, cbor_parser_init() + */ + +/** + * \addtogroup CborToJson + * @{ + *

Conversion limitations

+ * + * When converting from CBOR to JSON, there may be information loss. This + * section lists the possible scenarios. + * + * \par Number precision: + * ALL JSON numbers, due to its JavaScript heritage, are IEEE 754 + * double-precision floating point. This means JSON is not capable of + * representing all integers numbers outside the range [-(253)+1, + * 253-1] and is not capable of representing NaN or infinite. If the + * CBOR data contains a number outside the valid range, the conversion will + * lose precision. If the input was NaN or infinite, the result of the + * conversion will be the JSON null value. In addition, the distinction between + * half-, single- and double-precision is lost. + * + * \par + * If enabled, the original value and original type are stored in the metadata. + * + * \par Non-native types: + * CBOR's type system is richer than JSON's, which means some data values + * cannot be represented when converted to JSON. The conversion silently turns + * them into strings: CBOR simple types become "simple(nn)" where \c nn is the + * simple type's value, with the exception of CBOR undefined, which becomes + * "undefined", while CBOR byte strings are converted to an Base16, Base64, or + * Base64url encoding + * + * \par + * If enabled, the original type is stored in the metadata. + * + * \par Presence of tags: + * JSON has no support for tagged values, so by default tags are dropped when + * converting to JSON. However, if the CborConvertObeyByteStringTags option is + * active (default), then certain known tags are honored and are used to format + * the conversion of the tagged byte string to JSON. + * + * \par + * If the CborConvertTagsToObjects option is active, then the tag and the + * tagged value are converted to a JSON object. Otherwise, if enabled, the + * last (innermost) tag is stored in the metadata. + * + * \par Non-string keys in maps: + * JSON requires all Object keys to be strings, while CBOR does not. By + * default, if a non-string key is found, the conversion fails with error + * CborErrorJsonObjectKeyNotString. If the CborConvertStringifyMapKeys option + * is active, then the conversion attempts to create a string representation + * using CborPretty. Note that the \c json2cbor tool is not able to parse this + * back to the original form. + * + * \par Duplicate keys in maps: + * Neither JSON nor CBOR allow duplicated keys, but current TinyCBOR does not + * validate that this is the case. If there are duplicated keys in the input, + * they will be repeated in the output, which many JSON tools may flag as + * invalid. In addition to that, if the CborConvertStringifyMapKeys option is + * active, it is possible that a non-string key in a CBOR map will be converted + * to a string form that is identical to another key. + * + * \par + * When metadata support is active, the conversion will add extra key-value + * pairs to the JSON output so it can store the metadata. It is possible that + * the keys for the metadata clash with existing keys in the JSON map. + */ + +extern FILE *open_memstream(char **bufptr, size_t *sizeptr); + +enum ConversionStatusFlags { + TypeWasNotNative = 0x100, /* anything but strings, boolean, null, arrays and maps */ + TypeWasTagged = 0x200, + NumberPrecisionWasLost = 0x400, + NumberWasNaN = 0x800, + NumberWasInfinite = 0x1000, + NumberWasNegative = 0x2000, /* only used with NumberWasInifite or NumberWasTooBig */ + + FinalTypeMask = 0xff +}; + +typedef struct ConversionStatus { + CborTag lastTag; + uint64_t originalNumber; + int flags; +} ConversionStatus; + +static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status); + +static CborError dump_bytestring_base16(char **result, CborValue *it) +{ + static const char characters[] = "0123456789abcdef"; + size_t i; + size_t n = 0; + uint8_t *buffer; + CborError err = cbor_value_calculate_string_length(it, &n); + if (err) + return err; + + /* a Base16 (hex) output is twice as big as our buffer */ + buffer = (uint8_t *)malloc(n * 2 + 1); + *result = (char *)buffer; + + /* let cbor_value_copy_byte_string know we have an extra byte for the terminating NUL */ + ++n; + err = cbor_value_copy_byte_string(it, buffer + n - 1, &n, it); + cbor_assert(err == CborNoError); + + for (i = 0; i < n; ++i) { + uint8_t byte = buffer[n + i]; + buffer[2*i] = characters[byte >> 4]; + buffer[2*i + 1] = characters[byte & 0xf]; + } + return CborNoError; +} + +static CborError generic_dump_base64(char **result, CborValue *it, const char alphabet[65]) +{ + size_t n = 0, i; + uint8_t *buffer, *out, *in; + CborError err = cbor_value_calculate_string_length(it, &n); + if (err) + return err; + + /* a Base64 output (untruncated) has 4 bytes for every 3 in the input */ + size_t len = (n + 5) / 3 * 4; + out = buffer = (uint8_t *)malloc(len + 1); + *result = (char *)buffer; + + /* we read our byte string at the tail end of the buffer + * so we can do an in-place conversion while iterating forwards */ + in = buffer + len - n; + + /* let cbor_value_copy_byte_string know we have an extra byte for the terminating NUL */ + ++n; + err = cbor_value_copy_byte_string(it, in, &n, it); + cbor_assert(err == CborNoError); + + uint_least32_t val = 0; + for (i = 0; n - i >= 3; i += 3) { + /* read 3 bytes x 8 bits = 24 bits */ + if (false) { +#ifdef __GNUC__ + } else if (i) { + __builtin_memcpy(&val, in + i - 1, sizeof(val)); + val = cbor_ntohl(val); +#endif + } else { + val = (in[i] << 16) | (in[i + 1] << 8) | in[i + 2]; + } + + /* write 4 chars x 6 bits = 24 bits */ + *out++ = alphabet[(val >> 18) & 0x3f]; + *out++ = alphabet[(val >> 12) & 0x3f]; + *out++ = alphabet[(val >> 6) & 0x3f]; + *out++ = alphabet[val & 0x3f]; + } + + /* maybe 1 or 2 bytes left */ + if (n - i) { + /* we can read in[i + 1] even if it's past the end of the string because + * we know (by construction) that it's a NUL byte */ +#ifdef __GNUC__ + uint16_t val16; + __builtin_memcpy(&val16, in + i, sizeof(val16)); + val = cbor_ntohs(val16); +#else + val = (in[i] << 8) | in[i + 1]; +#endif + val <<= 8; + + /* the 65th character in the alphabet is our filler: either '=' or '\0' */ + out[4] = '\0'; + out[3] = alphabet[64]; + if (n - i == 2) { + /* write the third char in 3 chars x 6 bits = 18 bits */ + out[2] = alphabet[(val >> 6) & 0x3f]; + } else { + out[2] = alphabet[64]; /* filler */ + } + out[1] = alphabet[(val >> 12) & 0x3f]; + out[0] = alphabet[(val >> 18) & 0x3f]; + } else { + out[0] = '\0'; + } + + return CborNoError; +} + +static CborError dump_bytestring_base64(char **result, CborValue *it) +{ + static const char alphabet[] = "ABCDEFGH" "IJKLMNOP" "QRSTUVWX" "YZabcdef" + "ghijklmn" "opqrstuv" "wxyz0123" "456789+/" "="; + return generic_dump_base64(result, it, alphabet); +} + +static CborError dump_bytestring_base64url(char **result, CborValue *it) +{ + static const char alphabet[] = "ABCDEFGH" "IJKLMNOP" "QRSTUVWX" "YZabcdef" + "ghijklmn" "opqrstuv" "wxyz0123" "456789-_"; + return generic_dump_base64(result, it, alphabet); +} + +static CborError add_value_metadata(FILE *out, CborType type, const ConversionStatus *status) +{ + int flags = status->flags; + if (flags & TypeWasTagged) { + /* extract the tagged type, which may be JSON native */ + type = flags & FinalTypeMask; + flags &= ~(FinalTypeMask | TypeWasTagged); + + if (fprintf(out, "\"tag\":\"%" PRIu64 "\"%s", status->lastTag, + flags & ~TypeWasTagged ? "," : "") < 0) + return CborErrorIO; + } + + if (!flags) + return CborNoError; + + /* print at least the type */ + if (fprintf(out, "\"t\":%d", type) < 0) + return CborErrorIO; + + if (flags & NumberWasNaN) + if (fprintf(out, ",\"v\":\"nan\"") < 0) + return CborErrorIO; + if (flags & NumberWasInfinite) + if (fprintf(out, ",\"v\":\"%sinf\"", flags & NumberWasNegative ? "-" : "") < 0) + return CborErrorIO; + if (flags & NumberPrecisionWasLost) + if (fprintf(out, ",\"v\":\"%c%" PRIx64 "\"", flags & NumberWasNegative ? '-' : '+', + status->originalNumber) < 0) + return CborErrorIO; + if (type == CborSimpleType) + if (fprintf(out, ",\"v\":%d", (int)status->originalNumber) < 0) + return CborErrorIO; + return CborNoError; +} + +static CborError find_tagged_type(CborValue *it, CborTag *tag, CborType *type) +{ + CborError err = CborNoError; + *type = cbor_value_get_type(it); + while (*type == CborTagType) { + cbor_value_get_tag(it, tag); /* can't fail */ + err = cbor_value_advance_fixed(it); + if (err) + return err; + + *type = cbor_value_get_type(it); + } + return err; +} + +static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +{ + CborTag tag; + CborError err; + + if (flags & CborConvertTagsToObjects) { + cbor_value_get_tag(it, &tag); /* can't fail */ + err = cbor_value_advance_fixed(it); + if (err) + return err; + + if (fprintf(out, "{\"tag%" PRIu64 "\":", tag) < 0) + return CborErrorIO; + + CborType type = cbor_value_get_type(it); + err = value_to_json(out, it, flags, type, status); + if (err) + return err; + if (flags & CborConvertAddMetadata && status->flags) { + if (fprintf(out, ",\"tag%" PRIu64 "$cbor\":{", tag) < 0 || + add_value_metadata(out, type, status) != CborNoError || + fputc('}', out) < 0) + return CborErrorIO; + } + if (fputc('}', out) < 0) + return CborErrorIO; + status->flags = TypeWasNotNative | CborTagType; + return CborNoError; + } + + CborType type; + err = find_tagged_type(it, &status->lastTag, &type); + if (err) + return err; + tag = status->lastTag; + + /* special handling of byte strings? */ + if (type == CborByteStringType && (flags & CborConvertByteStringsToBase64Url) == 0 && + (tag == CborNegativeBignumTag || tag == CborExpectedBase16Tag || tag == CborExpectedBase64Tag)) { + char *str; + char *pre = ""; + + if (tag == CborNegativeBignumTag) { + pre = "~"; + err = dump_bytestring_base64url(&str, it); + } else if (tag == CborExpectedBase64Tag) { + err = dump_bytestring_base64(&str, it); + } else { /* tag == CborExpectedBase16Tag */ + err = dump_bytestring_base16(&str, it); + } + if (err) + return err; + err = fprintf(out, "\"%s%s\"", pre, str) < 0 ? CborErrorIO : CborNoError; + free(str); + status->flags = TypeWasNotNative | TypeWasTagged | CborByteStringType; + return err; + } + + /* no special handling */ + err = value_to_json(out, it, flags, type, status); + status->flags |= TypeWasTagged | type; + return err; +} + +static CborError stringify_map_key(char **key, CborValue *it, int flags, CborType type) +{ + (void)flags; /* unused */ + (void)type; /* unused */ +#ifdef WITHOUT_OPEN_MEMSTREAM + (void)key; /* unused */ + (void)it; /* unused */ + return CborErrorJsonNotImplemented; +#else + size_t size; + + FILE *memstream = open_memstream(key, &size); + if (memstream == NULL) + return CborErrorOutOfMemory; /* could also be EMFILE, but it's unlikely */ + CborError err = cbor_value_to_pretty_advance(memstream, it); + + if (unlikely(fclose(memstream) < 0 || *key == NULL)) + return CborErrorInternalError; + return err; +#endif +} + +static CborError array_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +{ + const char *comma = ""; + while (!cbor_value_at_end(it)) { + if (fprintf(out, "%s", comma) < 0) + return CborErrorIO; + comma = ","; + + CborError err = value_to_json(out, it, flags, cbor_value_get_type(it), status); + if (err) + return err; + } + return CborNoError; +} + +static CborError map_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +{ + const char *comma = ""; + CborError err; + while (!cbor_value_at_end(it)) { + char *key; + if (fprintf(out, "%s", comma) < 0) + return CborErrorIO; + comma = ","; + + CborType keyType = cbor_value_get_type(it); + if (likely(keyType == CborTextStringType)) { + size_t n = 0; + err = cbor_value_dup_text_string(it, &key, &n, it); + } else if (flags & CborConvertStringifyMapKeys) { + err = stringify_map_key(&key, it, flags, keyType); + } else { + return CborErrorJsonObjectKeyNotString; + } + if (err) + return err; + + /* first, print the key */ + if (fprintf(out, "\"%s\":", key) < 0) { + free(key); + return CborErrorIO; + } + + /* then, print the value */ + CborType valueType = cbor_value_get_type(it); + err = value_to_json(out, it, flags, valueType, status); + + /* finally, print any metadata we may have */ + if (flags & CborConvertAddMetadata) { + if (!err && keyType != CborTextStringType) { + if (fprintf(out, ",\"%s$keycbordump\":true", key) < 0) + err = CborErrorIO; + } + if (!err && status->flags) { + if (fprintf(out, ",\"%s$cbor\":{", key) < 0 || + add_value_metadata(out, valueType, status) != CborNoError || + fputc('}', out) < 0) + err = CborErrorIO; + } + } + + free(key); + if (err) + return err; + } + return CborNoError; +} + +static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status) +{ + CborError err; + status->flags = 0; + + switch (type) { + case CborArrayType: + case CborMapType: { + /* recursive type */ + CborValue recursed; + err = cbor_value_enter_container(it, &recursed); + if (err) { + it->ptr = recursed.ptr; + return err; /* parse error */ + } + if (fputc(type == CborArrayType ? '[' : '{', out) < 0) + return CborErrorIO; + + err = (type == CborArrayType) ? + array_to_json(out, &recursed, flags, status) : + map_to_json(out, &recursed, flags, status); + if (err) { + it->ptr = recursed.ptr; + return err; /* parse error */ + } + + if (fputc(type == CborArrayType ? ']' : '}', out) < 0) + return CborErrorIO; + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; /* parse error */ + + status->flags = 0; /* reset, there are never conversion errors for us */ + return CborNoError; + } + + case CborIntegerType: { + double num; /* JS numbers are IEEE double precision */ + uint64_t val; + cbor_value_get_raw_integer(it, &val); /* can't fail */ + num = (double)val; + + if (cbor_value_is_negative_integer(it)) { + num = -num - 1; /* convert to negative */ + if ((uint64_t)(-num - 1) != val) { + status->flags = NumberPrecisionWasLost | NumberWasNegative; + status->originalNumber = val; + } + } else { + if ((uint64_t)num != val) { + status->flags = NumberPrecisionWasLost; + status->originalNumber = val; + } + } + if (fprintf(out, "%.0f", num) < 0) /* this number has no fraction, so no decimal points please */ + return CborErrorIO; + break; + } + + case CborByteStringType: + case CborTextStringType: { + char *str; + if (type == CborByteStringType) { + err = dump_bytestring_base64url(&str, it); + status->flags = TypeWasNotNative; + } else { + size_t n = 0; + err = cbor_value_dup_text_string(it, &str, &n, it); + } + if (err) + return err; + err = (fprintf(out, "\"%s\"", str) < 0) ? CborErrorIO : CborNoError; + free(str); + return err; + } + + case CborTagType: + return tagged_value_to_json(out, it, flags, status); + + case CborSimpleType: { + uint8_t simple_type; + cbor_value_get_simple_type(it, &simple_type); /* can't fail */ + status->flags = TypeWasNotNative; + status->originalNumber = simple_type; + if (fprintf(out, "\"simple(%" PRIu8 ")\"", simple_type) < 0) + return CborErrorIO; + break; + } + + case CborNullType: + if (fprintf(out, "null") < 0) + return CborErrorIO; + break; + + case CborUndefinedType: + status->flags = TypeWasNotNative; + if (fprintf(out, "\"undefined\"") < 0) + return CborErrorIO; + break; + + case CborBooleanType: { + bool val; + cbor_value_get_boolean(it, &val); /* can't fail */ + if (fprintf(out, val ? "true" : "false") < 0) + return CborErrorIO; + break; + } + +#ifndef CBOR_NO_FLOATING_POINT + case CborDoubleType: { + double val; + if (false) { + float f; + case CborFloatType: + status->flags = TypeWasNotNative; + cbor_value_get_float(it, &f); + val = f; + } else if (false) { + uint16_t f16; + case CborHalfFloatType: +# ifndef CBOR_NO_HALF_FLOAT_TYPE + status->flags = TypeWasNotNative; + cbor_value_get_half_float(it, &f16); + val = decode_half(f16); +# else + (void)f16; + err = CborErrorUnsupportedType; + break; +# endif + } else { + cbor_value_get_double(it, &val); + } + + int r = fpclassify(val); + if (r == FP_NAN || r == FP_INFINITE) { + if (fprintf(out, "null") < 0) + return CborErrorIO; + status->flags |= r == FP_NAN ? NumberWasNaN : + NumberWasInfinite | (val < 0 ? NumberWasNegative : 0); + } else { + uint64_t ival = (uint64_t)fabs(val); + if ((double)ival == fabs(val)) { + /* print as integer so we get the full precision */ + r = fprintf(out, "%s%" PRIu64, val < 0 ? "-" : "", ival); + status->flags |= TypeWasNotNative; /* mark this integer number as a double */ + } else { + /* this number is definitely not a 64-bit integer */ + r = fprintf(out, "%." DBL_DECIMAL_DIG_STR "g", val); + } + if (r < 0) + return CborErrorIO; + } + break; + } +#else + case CborDoubleType: + case CborFloatType: + case CborHalfFloatType: + err = CborErrorUnsupportedType; + break; +#endif /* !CBOR_NO_FLOATING_POINT */ + + case CborInvalidType: + return CborErrorUnknownType; + } + + return cbor_value_advance_fixed(it); +} + +/** + * \enum CborToJsonFlags + * The CborToJsonFlags enum contains flags that control the conversion of CBOR to JSON. + * + * \value CborConvertAddMetadata Adds metadata to facilitate restoration of the original CBOR data. + * \value CborConvertTagsToObjects Converts CBOR tags to JSON objects + * \value CborConvertIgnoreTags (default) Ignore CBOR tags, except for byte strings + * \value CborConvertObeyByteStringTags (default) Honor formatting of CBOR byte strings if so tagged + * \value CborConvertByteStringsToBase64Url Force the conversion of all CBOR byte strings to Base64url encoding, despite any tags + * \value CborConvertRequireMapStringKeys (default) Require CBOR map keys to be strings, failing the conversion if they are not + * \value CborConvertStringifyMapKeys Convert non-string keys in CBOR maps to a string form + * \value CborConvertDefaultFlags Default conversion flags. + */ + +/** + * \fn CborError cbor_value_to_json(FILE *out, const CborValue *value, int flags) + * + * Converts the current CBOR type pointed to by \a value to JSON and writes that + * to the \a out stream. If an error occurs, this function returns an error + * code similar to CborParsing. The \a flags parameter indicates one or more of + * the flags from CborToJsonFlags that control the conversion. + * + * \sa cbor_value_to_json_advance(), cbor_value_to_pretty() + */ + +/** + * Converts the current CBOR type pointed to by \a value to JSON and writes that + * to the \a out stream. If an error occurs, this function returns an error + * code similar to CborParsing. The \a flags parameter indicates one or more of + * the flags from CborToJsonFlags that control the conversion. + * + * If no error ocurred, this function advances \a value to the next element. + * + * \sa cbor_value_to_json(), cbor_value_to_pretty_advance() + */ +CborError cbor_value_to_json_advance(FILE *out, CborValue *value, int flags) +{ + ConversionStatus status; + return value_to_json(out, value, flags, cbor_value_get_type(value), &status); +} + +/** @} */ diff --git a/client/tinycbor/cborvalidation.c b/client/tinycbor/cborvalidation.c new file mode 100644 index 00000000..08c35117 --- /dev/null +++ b/client/tinycbor/cborvalidation.c @@ -0,0 +1,666 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" +#include "utf8_p.h" + +#include + +#ifndef CBOR_NO_FLOATING_POINT +# include +# include +#endif + + +#ifndef CBOR_PARSER_MAX_RECURSIONS +# define CBOR_PARSER_MAX_RECURSIONS 1024 +#endif + +/** + * \addtogroup CborParsing + * @{ + */ + +/** + * \enum CborValidationFlags + * The CborValidationFlags enum contains flags that control the validation of a + * CBOR stream. + * + * \value CborValidateBasic Validates only the syntactic correctedness of the stream. + * \value CborValidateCanonical Validates that the stream is in canonical format, according to + * RFC 7049 section 3.9. + * \value CborValidateStrictMode Performs strict validation, according to RFC 7049 section 3.10. + * \value CborValidateStrictest Attempt to perform the strictest validation we know of. + * + * \value CborValidateShortestIntegrals (Canonical) Validate that integral numbers and lengths are + * enconded in their shortest form possible. + * \value CborValidateShortestFloatingPoint (Canonical) Validate that floating-point numbers are encoded + * in their shortest form possible. + * \value CborValidateShortestNumbers (Canonical) Validate both integral and floating-point numbers + * are in their shortest form possible. + * \value CborValidateNoIndeterminateLength (Canonical) Validate that no string, array or map uses + * indeterminate length encoding. + * \value CborValidateMapIsSorted (Canonical & Strict mode) Validate that map keys appear in + * sorted order. + * \value CborValidateMapKeysAreUnique (Strict mode) Validate that map keys are unique. + * \value CborValidateTagUse (Strict mode) Validate that known tags are used with the + * correct types. This does not validate that the content of + * those types is syntactically correct. For example, this + * option validates that tag 1 (DateTimeString) is used with + * a Text String, but it does not validate that the string is + * a valid date/time representation. + * \value CborValidateUtf8 (Strict mode) Validate that text strings are appropriately + * encoded in UTF-8. + * \value CborValidateMapKeysAreString Validate that all map keys are text strings. + * \value CborValidateNoUndefined Validate that no elements of type "undefined" are present. + * \value CborValidateNoTags Validate that no tags are used. + * \value CborValidateFiniteFloatingPoint Validate that all floating point numbers are finite (no NaN or + * infinities are allowed). + * \value CborValidateCompleteData Validate that the stream is complete and there is no more data + * in the buffer. + * \value CborValidateNoUnknownSimpleTypesSA Validate that all Standards Action simple types are registered + * with IANA. + * \value CborValidateNoUnknownSimpleTypes Validate that all simple types used are registered with IANA. + * \value CborValidateNoUnknownTagsSA Validate that all Standard Actions tags are registered with IANA. + * \value CborValidateNoUnknownTagsSR Validate that all Standard Actions and Specification Required tags + * are registered with IANA (see below for limitations). + * \value CborValidateNoUnkonwnTags Validate that all tags are registered with IANA + * (see below for limitations). + * + * \par Simple type registry + * The CBOR specification requires that registration for use of the first 19 + * simple types must be done by way of Standards Action. The rest of the simple + * types only require a specification. The official list can be obtained from + * https://www.iana.org/assignments/cbor-simple-values/cbor-simple-values.xhtml. + * + * \par + * There are no registered simple types recognized by this release of TinyCBOR + * (beyond those defined by RFC 7049). + * + * \par Tag registry + * The CBOR specification requires that registration for use of the first 23 + * tags must be done by way of Standards Action. The next up to tag 255 only + * require a specification. Finally, all other tags can be registered on a + * first-come-first-serve basis. The official list can be ontained from + * https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml. + * + * \par + * Given the variability of this list, TinyCBOR cannot recognize all tags + * registered with IANA. Instead, the implementation only recognizes tags + * that are backed by an RFC. + * + * \par + * These are the tags known to the current TinyCBOR release: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TagData ItemSemantics
0UTF-8 text stringStandard date/time string
1integerEpoch-based date/time
2byte stringPositive bignum
3byte stringNegative bignum
4arrayDecimal fraction
5arrayBigfloat
16arrayCOSE Single Recipient Encrypted Data Object (RFC 8152)
17arrayCOSE Mac w/o Recipients Object (RFC 8152)
18arrayCOSE Single Signer Data Object (RFC 8162)
21byte string, array, mapExpected conversion to base64url encoding
22byte string, array, mapExpected conversion to base64 encoding
23byte string, array, mapExpected conversion to base16 encoding
24byte stringEncoded CBOR data item
32UTF-8 text stringURI
33UTF-8 text stringbase64url
34UTF-8 text stringbase64
35UTF-8 text stringRegular expression
36UTF-8 text stringMIME message
96arrayCOSE Encrypted Data Object (RFC 8152)
97arrayCOSE MACed Data Object (RFC 8152)
98arrayCOSE Signed Data Object (RFC 8152)
55799anySelf-describe CBOR
+ */ + +struct KnownTagData { uint32_t tag; uint32_t types; }; +static const struct KnownTagData knownTagData[] = { + { 0, (uint32_t)CborTextStringType }, + { 1, (uint32_t)(CborIntegerType+1) }, + { 2, (uint32_t)CborByteStringType }, + { 3, (uint32_t)CborByteStringType }, + { 4, (uint32_t)CborArrayType }, + { 5, (uint32_t)CborArrayType }, + { 16, (uint32_t)CborArrayType }, + { 17, (uint32_t)CborArrayType }, + { 18, (uint32_t)CborArrayType }, + { 21, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 22, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 23, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 24, (uint32_t)CborByteStringType }, + { 32, (uint32_t)CborTextStringType }, + { 33, (uint32_t)CborTextStringType }, + { 34, (uint32_t)CborTextStringType }, + { 35, (uint32_t)CborTextStringType }, + { 36, (uint32_t)CborTextStringType }, + { 96, (uint32_t)CborArrayType }, + { 97, (uint32_t)CborArrayType }, + { 98, (uint32_t)CborArrayType }, + { 55799, 0U } +}; + +static CborError validate_value(CborValue *it, uint32_t flags, int recursionLeft); + +static inline CborError validate_utf8_string(const void *ptr, size_t n) +{ + const uint8_t *buffer = (const uint8_t *)ptr; + const uint8_t * const end = buffer + n; + while (buffer < end) { + uint32_t uc = get_utf8(&buffer, end); + if (uc == ~0U) + return CborErrorInvalidUtf8TextString; + } + return CborNoError; +} + +static inline CborError validate_simple_type(uint8_t simple_type, uint32_t flags) +{ + /* At current time, all known simple types are those from RFC 7049, + * which are parsed by the parser into different CBOR types. + * That means that if we've got here, the type is unknown */ + if (simple_type < 32) + return (flags & CborValidateNoUnknownSimpleTypesSA) ? CborErrorUnknownSimpleType : CborNoError; + return (flags & CborValidateNoUnknownSimpleTypes) == CborValidateNoUnknownSimpleTypes ? + CborErrorUnknownSimpleType : CborNoError; +} + +static inline CborError validate_number(const CborValue *it, CborType type, uint32_t flags) +{ + CborError err = CborNoError; + const uint8_t *ptr = it->ptr; + size_t bytesUsed, bytesNeeded; + uint64_t value; + + if ((flags & CborValidateShortestIntegrals) == 0) + return err; + if (type >= CborHalfFloatType && type <= CborDoubleType) + return err; /* checked elsewhere */ + + err = _cbor_value_extract_number(&ptr, it->parser->end, &value); + if (err) + return err; + + bytesUsed = (size_t)(ptr - it->ptr - 1); + bytesNeeded = 0; + if (value >= Value8Bit) + ++bytesNeeded; + if (value > 0xffU) + ++bytesNeeded; + if (value > 0xffffU) + bytesNeeded += 2; + if (value > 0xffffffffU) + bytesNeeded += 4; + if (bytesNeeded < bytesUsed) + return CborErrorOverlongEncoding; + return CborNoError; +} + +static inline CborError validate_tag(CborValue *it, CborTag tag, uint32_t flags, int recursionLeft) +{ + CborType type = cbor_value_get_type(it); + const size_t knownTagCount = sizeof(knownTagData) / sizeof(knownTagData[0]); + const struct KnownTagData *tagData = knownTagData; + const struct KnownTagData * const knownTagDataEnd = knownTagData + knownTagCount; + + if (!recursionLeft) + return CborErrorNestingTooDeep; + if (flags & CborValidateNoTags) + return CborErrorExcludedType; + + /* find the tag data, if any */ + for ( ; tagData != knownTagDataEnd; ++tagData) { + if (tagData->tag < tag) + continue; + if (tagData->tag > tag) + tagData = NULL; + break; + } + if (tagData == knownTagDataEnd) + tagData = NULL; + + if (flags & CborValidateNoUnknownTags && !tagData) { + /* tag not found */ + if (flags & CborValidateNoUnknownTagsSA && tag < 24) + return CborErrorUnknownTag; + if ((flags & CborValidateNoUnknownTagsSR) == CborValidateNoUnknownTagsSR && tag < 256) + return CborErrorUnknownTag; + if ((flags & CborValidateNoUnknownTags) == CborValidateNoUnknownTags) + return CborErrorUnknownTag; + } + + if (flags & CborValidateTagUse && tagData && tagData->types) { + uint32_t allowedTypes = tagData->types; + + /* correct Integer so it's not zero */ + if (type == CborIntegerType) + type = (CborType)(type + 1); + + while (allowedTypes) { + if ((uint8_t)(allowedTypes & 0xff) == type) + break; + allowedTypes >>= 8; + } + if (!allowedTypes) + return CborErrorInappropriateTagForType; + } + + return validate_value(it, flags, recursionLeft); +} + +#ifndef CBOR_NO_FLOATING_POINT +static inline CborError validate_floating_point(CborValue *it, CborType type, uint32_t flags) +{ + CborError err; + int r; + double val; + float valf; + uint16_t valf16; + + if (type != CborDoubleType) { + if (type == CborFloatType) { + err = cbor_value_get_float(it, &valf); + val = valf; + } else { +# ifdef CBOR_NO_HALF_FLOAT_TYPE + (void)valf16; + return CborErrorUnsupportedType; +# else + err = cbor_value_get_half_float(it, &valf16); + val = decode_half(valf16); +# endif + } + } else { + err = cbor_value_get_double(it, &val); + } + cbor_assert(err == CborNoError); /* can't fail */ + + r = fpclassify(val); + if (r == FP_NAN || r == FP_INFINITE) { + if (flags & CborValidateFiniteFloatingPoint) + return CborErrorExcludedValue; + if (flags & CborValidateShortestFloatingPoint) { + if (type == CborDoubleType) + return CborErrorOverlongEncoding; +# ifndef CBOR_NO_HALF_FLOAT_TYPE + if (type == CborFloatType) + return CborErrorOverlongEncoding; + if (r == FP_NAN && valf16 != 0x7e00) + return CborErrorImproperValue; + if (r == FP_INFINITE && valf16 != 0x7c00 && valf16 != 0xfc00) + return CborErrorImproperValue; +# endif + } + } + + if (flags & CborValidateShortestFloatingPoint && type > CborHalfFloatType) { + if (type == CborDoubleType) { + valf = (float)val; + if ((double)valf == val) + return CborErrorOverlongEncoding; + } +# ifndef CBOR_NO_HALF_FLOAT_TYPE + if (type == CborFloatType) { + valf16 = encode_half(valf); + if (valf == decode_half(valf16)) + return CborErrorOverlongEncoding; + } +# endif + } + + return CborNoError; +} +#endif + +static CborError validate_container(CborValue *it, int containerType, uint32_t flags, int recursionLeft) +{ + CborError err; + const uint8_t *previous = NULL; + const uint8_t *previous_end = NULL; + + if (!recursionLeft) + return CborErrorNestingTooDeep; + + while (!cbor_value_at_end(it)) { + const uint8_t *current = cbor_value_get_next_byte(it); + + if (containerType == CborMapType) { + if (flags & CborValidateMapKeysAreString) { + CborType type = cbor_value_get_type(it); + if (type == CborTagType) { + /* skip the tags */ + CborValue copy = *it; + err = cbor_value_skip_tag(©); + if (err) + return err; + type = cbor_value_get_type(©); + } + if (type != CborTextStringType) + return CborErrorMapKeyNotString; + } + } + + err = validate_value(it, flags, recursionLeft); + if (err) + return err; + + if (containerType != CborMapType) + continue; + + if (flags & CborValidateMapIsSorted) { + if (previous) { + uint64_t len1, len2; + const uint8_t *ptr; + + /* extract the two lengths */ + ptr = previous; + _cbor_value_extract_number(&ptr, it->parser->end, &len1); + ptr = current; + _cbor_value_extract_number(&ptr, it->parser->end, &len2); + + if (len1 > len2) + return CborErrorMapNotSorted; + if (len1 == len2) { + size_t bytelen1 = (size_t)(previous_end - previous); + size_t bytelen2 = (size_t)(it->ptr - current); + int r = memcmp(previous, current, bytelen1 <= bytelen2 ? bytelen1 : bytelen2); + + if (r == 0 && bytelen1 != bytelen2) + r = bytelen1 < bytelen2 ? -1 : +1; + if (r > 0) + return CborErrorMapNotSorted; + if (r == 0 && (flags & CborValidateMapKeysAreUnique) == CborValidateMapKeysAreUnique) + return CborErrorMapKeysNotUnique; + } + } + + previous = current; + previous_end = it->ptr; + } + + /* map: that was the key, so get the value */ + err = validate_value(it, flags, recursionLeft); + if (err) + return err; + } + return CborNoError; +} + +static CborError validate_value(CborValue *it, uint32_t flags, int recursionLeft) +{ + CborError err; + CborType type = cbor_value_get_type(it); + + if (cbor_value_is_length_known(it)) { + err = validate_number(it, type, flags); + if (err) + return err; + } else { + if (flags & CborValidateNoIndeterminateLength) + return CborErrorUnknownLength; + } + + switch (type) { + case CborArrayType: + case CborMapType: { + /* recursive type */ + CborValue recursed; + err = cbor_value_enter_container(it, &recursed); + if (!err) + err = validate_container(&recursed, type, flags, recursionLeft - 1); + if (err) { + it->ptr = recursed.ptr; + return err; + } + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; + return CborNoError; + } + + case CborIntegerType: { + uint64_t val; + err = cbor_value_get_raw_integer(it, &val); + cbor_assert(err == CborNoError); /* can't fail */ + + break; + } + + case CborByteStringType: + case CborTextStringType: { + size_t n = 0; + const void *ptr; + + err = _cbor_value_prepare_string_iteration(it); + if (err) + return err; + + while (1) { + err = validate_number(it, type, flags); + if (err) + return err; + + err = _cbor_value_get_string_chunk(it, &ptr, &n, it); + if (err) + return err; + if (!ptr) + break; + + if (type == CborTextStringType && flags & CborValidateUtf8) { + err = validate_utf8_string(ptr, n); + if (err) + return err; + } + } + + return CborNoError; + } + + case CborTagType: { + CborTag tag; + err = cbor_value_get_tag(it, &tag); + cbor_assert(err == CborNoError); /* can't fail */ + + err = cbor_value_advance_fixed(it); + if (err) + return err; + err = validate_tag(it, tag, flags, recursionLeft - 1); + if (err) + return err; + + return CborNoError; + } + + case CborSimpleType: { + uint8_t simple_type; + err = cbor_value_get_simple_type(it, &simple_type); + cbor_assert(err == CborNoError); /* can't fail */ + err = validate_simple_type(simple_type, flags); + if (err) + return err; + break; + } + + case CborNullType: + case CborBooleanType: + break; + + case CborUndefinedType: + if (flags & CborValidateNoUndefined) + return CborErrorExcludedType; + break; + + case CborHalfFloatType: + case CborFloatType: + case CborDoubleType: { +#ifdef CBOR_NO_FLOATING_POINT + return CborErrorUnsupportedType; +#else + err = validate_floating_point(it, type, flags); + if (err) + return err; + break; +#endif /* !CBOR_NO_FLOATING_POINT */ + } + + case CborInvalidType: + return CborErrorUnknownType; + } + + err = cbor_value_advance_fixed(it); + return err; +} + +/** + * Performs a full validation, controlled by the \a flags options, of the CBOR + * stream pointed by \a it and returns the error it found. If no error was + * found, it returns CborNoError and the application can iterate over the items + * with certainty that no errors will appear during parsing. + * + * If \a flags is CborValidateBasic, the result should be the same as + * cbor_value_validate_basic(). + * + * This function has the same timing and memory requirements as + * cbor_value_advance() and cbor_value_validate_basic(). + * + * \sa CborValidationFlags, cbor_value_validate_basic(), cbor_value_advance() + */ +CborError cbor_value_validate(const CborValue *it, uint32_t flags) +{ + CborValue value = *it; + CborError err = validate_value(&value, flags, CBOR_PARSER_MAX_RECURSIONS); + if (err) + return err; + if (flags & CborValidateCompleteData && it->ptr != it->parser->end) + return CborErrorGarbageAtEnd; + return CborNoError; +} + +/** + * @} + */ diff --git a/client/tinycbor/compilersupport_p.h b/client/tinycbor/compilersupport_p.h new file mode 100644 index 00000000..bd10efc9 --- /dev/null +++ b/client/tinycbor/compilersupport_p.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef COMPILERSUPPORT_H +#define COMPILERSUPPORT_H + +#include "cbor.h" + +#ifndef _BSD_SOURCE +# define _BSD_SOURCE +#endif +#ifndef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE +#endif +#ifndef assert +# include +#endif +#include +#include +#include + +#ifndef __cplusplus +# include +#endif + +#if __STDC_VERSION__ >= 201112L || __cplusplus >= 201103L || __cpp_static_assert >= 200410 +# define cbor_static_assert(x) static_assert(x, #x) +#elif !defined(__cplusplus) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 406) && (__STDC_VERSION__ > 199901L) +# define cbor_static_assert(x) _Static_assert(x, #x) +#else +# define cbor_static_assert(x) ((void)sizeof(char[2*!!(x) - 1])) +#endif +#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) +/* inline is a keyword */ +#else +/* use the definition from cbor.h */ +# define inline CBOR_INLINE +#endif + +#ifdef NDEBUG +# define cbor_assert(cond) do { if (!(cond)) unreachable(); } while (0) +#else +# define cbor_assert(cond) assert(cond) +#endif + +#ifndef STRINGIFY +#define STRINGIFY(x) STRINGIFY2(x) +#endif +#define STRINGIFY2(x) #x + +#if !defined(UINT32_MAX) || !defined(INT64_MAX) +/* C89? We can define UINT32_MAX portably, but not INT64_MAX */ +# error "Your system has stdint.h but that doesn't define UINT32_MAX or INT64_MAX" +#endif + +#ifndef DBL_DECIMAL_DIG +/* DBL_DECIMAL_DIG is C11 */ +# define DBL_DECIMAL_DIG 17 +#endif +#define DBL_DECIMAL_DIG_STR STRINGIFY(DBL_DECIMAL_DIG) + +#if defined(__GNUC__) && defined(__i386__) && !defined(__iamcu__) +# define CBOR_INTERNAL_API_CC __attribute__((regparm(3))) +#elif defined(_MSC_VER) && defined(_M_IX86) +# define CBOR_INTERNAL_API_CC __fastcall +#else +# define CBOR_INTERNAL_API_CC +#endif + +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +#if (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) || \ + (__has_builtin(__builtin_bswap64) && __has_builtin(__builtin_bswap32)) +# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define cbor_ntohll __builtin_bswap64 +# define cbor_htonll __builtin_bswap64 +# define cbor_ntohl __builtin_bswap32 +# define cbor_htonl __builtin_bswap32 +# ifdef __INTEL_COMPILER +# define cbor_ntohs _bswap16 +# define cbor_htons _bswap16 +# elif (__GNUC__ * 100 + __GNUC_MINOR__ >= 608) || __has_builtin(__builtin_bswap16) +# define cbor_ntohs __builtin_bswap16 +# define cbor_htons __builtin_bswap16 +# else +# define cbor_ntohs(x) (((uint16_t)(x) >> 8) | ((uint16_t)(x) << 8)) +# define cbor_htons cbor_ntohs +# endif +# else +# define cbor_ntohll +# define cbor_htonll +# define cbor_ntohl +# define cbor_htonl +# define cbor_ntohs +# define cbor_htons +# endif +#elif defined(__sun) +# include +#elif defined(_MSC_VER) +/* MSVC, which implies Windows, which implies little-endian and sizeof(long) == 4 */ +# include +# define cbor_ntohll _byteswap_uint64 +# define cbor_htonll _byteswap_uint64 +# define cbor_ntohl _byteswap_ulong +# define cbor_htonl _byteswap_ulong +# define cbor_ntohs _byteswap_ushort +# define cbor_htons _byteswap_ushort +#endif +#ifndef cbor_ntohs +# include +# define cbor_ntohs ntohs +# define cbor_htons htons +#endif +#ifndef cbor_ntohl +# include +# define cbor_ntohl ntohl +# define cbor_htonl htonl +#endif +#ifndef cbor_ntohll +# define cbor_ntohll ntohll +# define cbor_htonll htonll +/* ntohll isn't usually defined */ +# ifndef ntohll +# if (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ + (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(BYTE_ORDER) && defined(BIG_ENDIAN) && BYTE_ORDER == BIG_ENDIAN) || \ + (defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)) || (defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)) || \ + defined(__ARMEB__) || defined(__MIPSEB__) || defined(__s390__) || defined(__sparc__) +# define ntohll +# define htonll +# elif (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ + (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && BYTE_ORDER == LITTLE_ENDIAN) || \ + defined(_LITTLE_ENDIAN) || defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__MIPSEL__) || \ + defined(__i386) || defined(__i386__) || defined(__x86_64) || defined(__x86_64__) || defined(__amd64) +# define ntohll(x) ((ntohl((uint32_t)(x)) * UINT64_C(0x100000000)) + (ntohl((x) >> 32))) +# define htonll ntohll +# else +# error "Unable to determine byte order!" +# endif +# endif +#endif + + +#ifdef __cplusplus +# define CONST_CAST(t, v) const_cast(v) +#else +/* C-style const_cast without triggering a warning with -Wcast-qual */ +# define CONST_CAST(t, v) (t)(uintptr_t)(v) +#endif + +#ifdef __GNUC__ +#ifndef likely +# define likely(x) __builtin_expect(!!(x), 1) +#endif +#ifndef unlikely +# define unlikely(x) __builtin_expect(!!(x), 0) +#endif +# define unreachable() __builtin_unreachable() +#elif defined(_MSC_VER) +# define likely(x) (x) +# define unlikely(x) (x) +# define unreachable() __assume(0) +#else +# define likely(x) (x) +# define unlikely(x) (x) +# define unreachable() do {} while (0) +#endif + +static inline bool add_check_overflow(size_t v1, size_t v2, size_t *r) +{ +#if ((defined(__GNUC__) && (__GNUC__ >= 5)) && !defined(__INTEL_COMPILER)) || __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(v1, v2, r); +#else + /* unsigned additions are well-defined */ + *r = v1 + v2; + return v1 > v1 + v2; +#endif +} + +#endif /* COMPILERSUPPORT_H */ + diff --git a/client/tinycbor/open_memstream.c b/client/tinycbor/open_memstream.c new file mode 100644 index 00000000..18f3de8b --- /dev/null +++ b/client/tinycbor/open_memstream.c @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#define _GNU_SOURCE 1 + +#include +#include +#include +#include +#include + +#if defined(__unix__) || defined(__APPLE__) +# include +#endif +#ifdef __APPLE__ +typedef int RetType; +typedef int LenType; +#elif __GLIBC__ +typedef ssize_t RetType; +typedef size_t LenType; +#else +# error "Cannot implement open_memstream!" +#endif + +#include "compilersupport_p.h" + +struct Buffer +{ + char **ptr; + size_t *len; + size_t alloc; +}; + +static RetType write_to_buffer(void *cookie, const char *data, LenType len) +{ + struct Buffer *b = (struct Buffer *)cookie; + char *ptr = *b->ptr; + size_t newsize; + + errno = EFBIG; + if (unlikely(add_check_overflow(*b->len, len, &newsize))) + return -1; + + if (newsize >= b->alloc) { // NB! one extra byte is needed to avoid buffer overflow at close_buffer + // make room + size_t newalloc = newsize + newsize / 2 + 1; // give 50% more room + ptr = realloc(ptr, newalloc); + if (ptr == NULL) + return -1; + b->alloc = newalloc; + *b->ptr = ptr; + } + + memcpy(ptr + *b->len, data, len); + *b->len = newsize; + return len; +} + +static int close_buffer(void *cookie) +{ + struct Buffer *b = (struct Buffer *)cookie; + if (*b->ptr) + (*b->ptr)[*b->len] = '\0'; + free(b); + return 0; +} + +FILE *open_memstream(char **bufptr, size_t *lenptr) +{ + struct Buffer *b = (struct Buffer *)malloc(sizeof(struct Buffer)); + if (b == NULL) + return NULL; + b->alloc = 0; + b->len = lenptr; + b->ptr = bufptr; + *bufptr = NULL; + *lenptr = 0; + +#ifdef __APPLE__ + return funopen(b, NULL, write_to_buffer, NULL, close_buffer); +#elif __GLIBC__ + static const cookie_io_functions_t vtable = { + NULL, + write_to_buffer, + NULL, + close_buffer + }; + return fopencookie(b, "w", vtable); +#endif +} + diff --git a/client/tinycbor/tinycbor-version.h b/client/tinycbor/tinycbor-version.h new file mode 100644 index 00000000..29967d02 --- /dev/null +++ b/client/tinycbor/tinycbor-version.h @@ -0,0 +1,3 @@ +#define TINYCBOR_VERSION_MAJOR 0 +#define TINYCBOR_VERSION_MINOR 5 +#define TINYCBOR_VERSION_PATCH 3 diff --git a/client/tinycbor/utf8_p.h b/client/tinycbor/utf8_p.h new file mode 100644 index 00000000..ca438350 --- /dev/null +++ b/client/tinycbor/utf8_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef CBOR_UTF8_H +#define CBOR_UTF8_H + +#include "compilersupport_p.h" + +#include + +static inline uint32_t get_utf8(const uint8_t **buffer, const uint8_t *end) +{ + int charsNeeded; + uint32_t uc, min_uc; + uint8_t b; + ptrdiff_t n = end - *buffer; + if (n == 0) + return ~0U; + + uc = *(*buffer)++; + if (uc < 0x80) { + /* single-byte UTF-8 */ + return uc; + } + + /* multi-byte UTF-8, decode it */ + if (unlikely(uc <= 0xC1)) + return ~0U; + if (uc < 0xE0) { + /* two-byte UTF-8 */ + charsNeeded = 2; + min_uc = 0x80; + uc &= 0x1f; + } else if (uc < 0xF0) { + /* three-byte UTF-8 */ + charsNeeded = 3; + min_uc = 0x800; + uc &= 0x0f; + } else if (uc < 0xF5) { + /* four-byte UTF-8 */ + charsNeeded = 4; + min_uc = 0x10000; + uc &= 0x07; + } else { + return ~0U; + } + + if (n < charsNeeded) + return ~0U; + + /* first continuation character */ + b = *(*buffer)++; + if ((b & 0xc0) != 0x80) + return ~0U; + uc <<= 6; + uc |= b & 0x3f; + + if (charsNeeded > 2) { + /* second continuation character */ + b = *(*buffer)++; + if ((b & 0xc0) != 0x80) + return ~0U; + uc <<= 6; + uc |= b & 0x3f; + + if (charsNeeded > 3) { + /* third continuation character */ + b = *(*buffer)++; + if ((b & 0xc0) != 0x80) + return ~0U; + uc <<= 6; + uc |= b & 0x3f; + } + } + + /* overlong sequence? surrogate pair? out or range? */ + if (uc < min_uc || uc - 0xd800U < 2048U || uc > 0x10ffff) + return ~0U; + + return uc; +} + +#endif /* CBOR_UTF8_H */ diff --git a/client/util.c b/client/util.c index 423d14a0..dec7c5a1 100644 --- a/client/util.c +++ b/client/util.c @@ -140,6 +140,17 @@ int FillBuffer(uint8_t *data, size_t maxDataLength, size_t *dataLength, ...) { return 0; } +bool CheckStringIsHEXValue(const char *value) { + for (int i = 0; i < strlen(value); i++) + if (!isxdigit(value[i])) + return false; + + if (strlen(value) % 2) + return false; + + return true; +} + void hex_to_buffer(const uint8_t *buf, const uint8_t *hex_data, const size_t hex_len, const size_t hex_max_len, const size_t min_str_len, const size_t spaces_between, bool uppercase) { diff --git a/client/util.h b/client/util.h index 1867519e..e3549c02 100644 --- a/client/util.h +++ b/client/util.h @@ -86,6 +86,7 @@ extern void FillFileNameByUID(char *fileName, uint8_t * uid, char *ext, int byte // fill buffer from structure [{uint8_t data, size_t length},...] extern int FillBuffer(uint8_t *data, size_t maxDataLength, size_t *dataLength, ...); +extern bool CheckStringIsHEXValue(const char *value); extern void hex_to_buffer(const uint8_t *buf, const uint8_t *hex_data, const size_t hex_len, const size_t hex_max_len, const size_t min_str_len, const size_t spaces_between, bool uppercase);