diff --git a/.gitignore b/.gitignore index 8ca8bbd..f9e3e3d 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,10 @@ tool/tests/parse_name tool/tests/parse_name.log tool/tests/parse_name.o tool/tests/parse_name.trs +tool/tests/test_inout +tool/tests/test_inout.log +tool/tests/test_inout.o +tool/tests/test_inout.trs coverage/ lib/error.gcno lib/version.gcno diff --git a/tool/Makefile.am b/tool/Makefile.am index b815bbb..392ac30 100644 --- a/tool/Makefile.am +++ b/tool/Makefile.am @@ -41,7 +41,7 @@ libpiv_cmd_la_SOURCES = cmdline.ggo cmdline.c cmdline.h libpiv_cmd_la_CFLAGS = libpiv_util_la_SOURCES = util.c util.h -libpiv_util_la_LIBADD = $(OPENSSL_LIBS) +libpiv_util_la_LIBADD = $(top_builddir)/lib/libykpiv.la $(OPENSSL_LIBS) cmdline.c cmdline.h: cmdline.ggo Makefile.am $(top_srcdir)/configure.ac $(GENGETOPT) --input $^ diff --git a/tool/cmdline.ggo b/tool/cmdline.ggo index 5252868..03e61c9 100644 --- a/tool/cmdline.ggo +++ b/tool/cmdline.ggo @@ -32,7 +32,8 @@ option "action" a "Action to take" values="version","generate","set-mgm-key", "reset","pin-retries","import-key","import-certificate","set-chuid", "request-certificate","verify-pin","change-pin","change-puk","unblock-pin", "selfsign-certificate","delete-certificate","read-certificate","status", - "test-signature","test-decipher","list-readers","set-ccc" enum multiple + "test-signature","test-decipher","list-readers","set-ccc","write-object", + "read-object" enum multiple text " Multiple actions may be given at once and will be executed in order for example --action=verify-pin --action=request-certificate\n" @@ -60,4 +61,6 @@ option "pin" P "Pin/puk code for verification" string optional option "new-pin" N "New pin/puk code for changing" string optional dependon="pin" option "pin-policy" - "Set pin policy for action generate or import-key" values="never","once","always" enum optional option "touch-policy" - "Set touch policy for action generate, import-key or set-mgm-key" values="never","always" enum optional +option "id" - "Id of object for write/read object" int optional +option "format" f "Format of data for write/read object" values="hex","base64","binary" enum optional default="hex" option "sign" - "Sign data" flag off hidden diff --git a/tool/tests/Makefile.am b/tool/tests/Makefile.am index 7148d1d..7c1ac31 100644 --- a/tool/tests/Makefile.am +++ b/tool/tests/Makefile.am @@ -37,8 +37,9 @@ AM_CPPFLAGS += $(OPENSSL_CFLAGS) AM_LDFLAGS = -no-install parse_name_LDADD = ../libpiv_util.la $(OPENSSL_LIBS) +test_inout_LDADD = ../libpiv_util.la -check_PROGRAMS = parse_name +check_PROGRAMS = parse_name test_inout TESTS = basic.sh $(check_PROGRAMS) if ENABLE_COV diff --git a/tool/tests/test_inout.c b/tool/tests/test_inout.c new file mode 100644 index 0000000..50a5ed2 --- /dev/null +++ b/tool/tests/test_inout.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015 Yubico AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include "util.h" + +#ifdef _WIN32 +#define pipe(fds) _pipe(fds,4096, 0) +#endif + +static void test_inout(enum enum_format format) { + const unsigned char buf[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + unsigned char buf2[sizeof(buf)]; + int pipefd[2]; + FILE *tmp1, *tmp2; + + assert(pipe(pipefd) == 0); + tmp1 = fdopen(pipefd[1], "w"); + dump_data(buf, sizeof(buf), tmp1, false, format); + fclose(tmp1); + tmp2 = fdopen(pipefd[0], "r"); + read_data(buf2, sizeof(buf2), tmp2, format); + assert(memcmp(buf, buf2, sizeof(buf)) == 0); + fclose(tmp2); +} + +int main(void) { + test_inout(format_arg_base64); + test_inout(format_arg_hex); + test_inout(format_arg_binary); + exit(0); +} diff --git a/tool/util.c b/tool/util.c index d330daa..9e3b675 100644 --- a/tool/util.c +++ b/tool/util.c @@ -148,12 +148,80 @@ parse_err: return NULL; } -void dump_hex(const unsigned char *buf, unsigned int len, FILE *output, bool space) { - unsigned int i; - for (i = 0; i < len; i++) { - fprintf(output, "%02x%s", buf[i], space == true ? " " : ""); +size_t read_data(unsigned char *buf, size_t len, FILE* input, enum enum_format format) { + char raw_buf[3072 * 2]; + size_t raw_len = sizeof(raw_buf); + raw_len = fread(raw_buf, 1, raw_len, input); + switch(format) { + case format_arg_hex: + if(raw_buf[raw_len - 1] == '\n') { + raw_len -= 1; + } + if(ykpiv_hex_decode(raw_buf, raw_len, buf, &len) != YKPIV_OK) { + return 0; + } + return len; + case format_arg_base64: + { + int read; + BIO *b64 = BIO_new(BIO_f_base64()); + BIO *bio = BIO_new_mem_buf(raw_buf, raw_len); + BIO_push(b64, bio); + read = BIO_read(b64, buf, len); + BIO_free_all(b64); + if(read <= 0) { + return 0; + } else { + return (size_t)read; + } + } + break; + case format_arg_binary: + if(raw_len > len) { + return 0; + } + memcpy(buf, raw_buf, raw_len); + return raw_len; + case format__NULL: + default: + return 0; + } +} + +void dump_data(const unsigned char *buf, unsigned int len, FILE *output, bool space, enum enum_format format) { + switch(format) { + case format_arg_hex: + { + char tmp[3072 * 3 + 1]; + unsigned int i; + int step = 2; + if(space) step += 1; + if(len > 3072) { + return; + } + for (i = 0; i < len; i++) { + sprintf(tmp + i * step, "%02x%s", buf[i], space == true ? " " : ""); + } + fprintf(output, "%s\n", tmp); + } + return; + case format_arg_base64: + { + BIO *b64 = BIO_new(BIO_f_base64()); + BIO *bio = BIO_new_fp(output, BIO_NOCLOSE); + BIO_push(b64, bio); + BIO_write(b64, buf, (int)len); + BIO_flush(b64); + BIO_free_all(b64); + } + return; + case format_arg_binary: + fwrite(buf, 1, len, output); + return; + case format__NULL: + default: + return; } - fprintf(output, "\n"); } int get_length(const unsigned char *buffer, int *len) { diff --git a/tool/util.h b/tool/util.h index 2dc893e..d9284f8 100644 --- a/tool/util.h +++ b/tool/util.h @@ -40,7 +40,8 @@ #define INPUT 1 #define OUTPUT 2 -void dump_hex(unsigned const char*, unsigned int, FILE*, bool); +size_t read_data(unsigned char*, size_t, FILE*, enum enum_format); +void dump_data(unsigned const char*, unsigned int, FILE*, bool, enum enum_format); int set_length(unsigned char*, int); int get_length(const unsigned char*, int*); X509_NAME *parse_name(const char*); diff --git a/tool/yubico-piv-tool.c b/tool/yubico-piv-tool.c index 0c00a59..3410134 100644 --- a/tool/yubico-piv-tool.c +++ b/tool/yubico-piv-tool.c @@ -631,7 +631,7 @@ static bool set_dataobject(ykpiv_state *state, int verbose, int type) { } if(verbose) { fprintf(stderr, "Setting the %s to: ", type == CHUID ? "CHUID" : "CCC"); - dump_hex(obj, len, stderr, true); + dump_data(obj, len, stderr, true, format_arg_hex); } if((res = ykpiv_save_object(state, id, obj, len)) != YKPIV_OK) { fprintf(stderr, "Failed communicating with device: %s\n", ykpiv_strerror(res)); @@ -1157,7 +1157,7 @@ static bool sign_file(ykpiv_state *state, const char *input, const char *output, if(verbosity) { fprintf(stderr, "file hashed as: "); - dump_hex(hashed, hash_len, stderr, true); + dump_data(hashed, hash_len, stderr, true, format_arg_hex); } EVP_MD_CTX_destroy(mdctx); } @@ -1176,7 +1176,7 @@ static bool sign_file(ykpiv_state *state, const char *input, const char *output, if(verbosity) { fprintf(stderr, "file signed as: "); - dump_hex(buf, len, stderr, true); + dump_data(buf, len, stderr, true, format_arg_hex); } fwrite(buf, 1, len, output_file); ret = true; @@ -1276,7 +1276,7 @@ static void print_cert_info(ykpiv_state *state, enum enum_slot slot, const EVP_M fprintf(output, "\n"); X509_digest(x509, md, data, &md_len); fprintf(output, "\tFingerprint:\t"); - dump_hex(data, md_len, output, false); + dump_data(data, md_len, output, false, format_arg_hex); bio = BIO_new_fp(output, BIO_NOCLOSE | BIO_FP_TEXT); not_before = X509_get_notBefore(x509); @@ -1325,7 +1325,7 @@ static bool status(ykpiv_state *state, enum enum_hash hash, if(ykpiv_fetch_object(state, YKPIV_OBJ_CHUID, chuid, &len) != YKPIV_OK) { fprintf(output_file, "No data available\n"); } else { - dump_hex(chuid, len, output_file, false); + dump_data(chuid, len, output_file, false, format_arg_hex); } if (slot == slot__NULL) @@ -1405,7 +1405,7 @@ static bool test_signature(ykpiv_state *state, enum enum_slot slot, EVP_DigestFinal_ex(mdctx, data, &data_len); if(verbose) { fprintf(stderr, "Test data hashes as: "); - dump_hex(data, data_len, stderr, true); + dump_data(data, data_len, stderr, true, format_arg_hex); } } @@ -1561,9 +1561,9 @@ static bool test_decipher(ykpiv_state *state, enum enum_slot slot, if(len == sizeof(secret)) { if(verbose) { fprintf(stderr, "Generated nonce: "); - dump_hex(secret, sizeof(secret), stderr, true); + dump_data(secret, sizeof(secret), stderr, true, format_arg_hex); fprintf(stderr, "Decrypted nonce: "); - dump_hex(secret2, sizeof(secret2), stderr, true); + dump_data(secret2, sizeof(secret2), stderr, true, format_arg_hex); } if(memcmp(secret, secret2, sizeof(secret)) == 0) { fprintf(stderr, "Successfully performed RSA decryption!\n"); @@ -1603,9 +1603,9 @@ static bool test_decipher(ykpiv_state *state, enum enum_slot slot, } if(verbose) { fprintf(stderr, "ECDH host generated: "); - dump_hex(secret, len, stderr, true); + dump_data(secret, len, stderr, true, format_arg_hex); fprintf(stderr, "ECDH card generated: "); - dump_hex(secret2, len, stderr, true); + dump_data(secret2, len, stderr, true, format_arg_hex); } if(memcmp(secret, secret2, key_len) == 0) { fprintf(stderr, "Successfully performed ECDH exchange with card.\n"); @@ -1644,6 +1644,73 @@ static bool list_readers(ykpiv_state *state) { return true; } +static bool write_object(ykpiv_state *state, int id, + const char *input_file_name, int verbosity, enum enum_format format) { + bool ret = false; + FILE *input_file = NULL; + unsigned char data[3072]; + size_t len = sizeof(data); + ykpiv_rc res; + + input_file = open_file(input_file_name, INPUT); + if(!input_file) { + return false; + } + + if(isatty(fileno(input_file))) { + fprintf(stderr, "Please paste the data...\n"); + } + + len = read_data(data, len, input_file, format); + if(len == 0) { + fprintf(stderr, "Failed reading data\n"); + goto write_out; + } + + if(verbosity) { + fprintf(stderr, "Writing %lu bytes of data to object %x.\n", len, id); + } + + if((res = ykpiv_save_object(state, id, data, len)) != YKPIV_OK) { + fprintf(stderr, "Failed writing data to device: %s\n", ykpiv_strerror(res)); + } else { + ret = true; + } + +write_out: + if(input_file != stdin) { + fclose(input_file); + } + return ret; +} + +static bool read_object(ykpiv_state *state, int id, const char *output_file_name, + enum enum_format format) { + FILE *output_file = NULL; + unsigned char data[3072]; + unsigned long len = sizeof(data); + bool ret = false; + + output_file = open_file(output_file_name, OUTPUT); + if(!output_file) { + return false; + } + + if(ykpiv_fetch_object(state, id, data, &len) != YKPIV_OK) { + fprintf(stderr, "Failed fetching object.\n"); + goto read_out; + } + + dump_data(data, len, output_file, false, format); + ret = true; + +read_out: + if(output_file != stdout) { + fclose(output_file); + } + return ret; +} + int main(int argc, char *argv[]) { struct gengetopt_args_info args_info; ykpiv_state *state; @@ -1688,6 +1755,14 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } break; + case action_arg_writeMINUS_object: + case action_arg_readMINUS_object: + if(!args_info.id_given) { + fprintf(stderr, "The '%s' action needs the --id argument.\n", + cmdline_parser_action_values[action]); + return EXIT_FAILURE; + } + break; case action_arg_changeMINUS_pin: case action_arg_changeMINUS_puk: case action_arg_unblockMINUS_pin: @@ -1727,6 +1802,7 @@ int main(int argc, char *argv[]) { case action_arg_setMINUS_chuid: case action_arg_setMINUS_ccc: case action_arg_deleteMINUS_certificate: + case action_arg_writeMINUS_object: if(verbosity) { fprintf(stderr, "Authenticating since action '%s' needs that.\n", cmdline_parser_action_values[action]); } @@ -1745,6 +1821,7 @@ int main(int argc, char *argv[]) { case action_arg_testMINUS_signature: case action_arg_testMINUS_decipher: case action_arg_listMINUS_readers: + case action_arg_readMINUS_object: case action__NULL: default: if(verbosity) { @@ -1945,6 +2022,17 @@ int main(int argc, char *argv[]) { if(list_readers(state) == false) { ret = EXIT_FAILURE; } + case action_arg_writeMINUS_object: + if(write_object(state, args_info.id_arg, args_info.input_arg, verbosity, + args_info.format_arg) == false) { + ret = EXIT_FAILURE; + } + break; + case action_arg_readMINUS_object: + if(read_object(state, args_info.id_arg, args_info.output_arg, + args_info.format_arg) == false) { + ret = EXIT_FAILURE; + } break; case action__NULL: default: diff --git a/ykcs11/ykcs11.c b/ykcs11/ykcs11.c index e4c5aae..b710ca7 100644 --- a/ykcs11/ykcs11.c +++ b/ykcs11/ykcs11.c @@ -1840,7 +1840,7 @@ CK_DEFINE_FUNCTION(CK_RV, C_Sign)( DBG("Sending %lu bytes to sign", ulDataLen); #if YKCS11_DBG == 1 - dump_hex(pData, ulDataLen, stderr, CK_TRUE); + dump_data(pData, ulDataLen, stderr, CK_TRUE, format_arg_hex); #endif if (is_hashed_mechanism(op_info.mechanism.mechanism) == CK_TRUE) { @@ -1882,7 +1882,7 @@ CK_DEFINE_FUNCTION(CK_RV, C_Sign)( DBG("Using key %lx", op_info.op.sign.key_id); DBG("After padding and transformation there are %lu bytes", op_info.buf_len); #if YKCS11_DBG == 1 - dump_hex(op_info.buf, op_info.buf_len, stderr, CK_TRUE); + dump_data(op_info.buf, op_info.buf_len, stderr, CK_TRUE, format_arg_hex); #endif *pulSignatureLen = sizeof(op_info.buf); @@ -1903,7 +1903,7 @@ CK_DEFINE_FUNCTION(CK_RV, C_Sign)( DBG("Got %lu bytes back", *pulSignatureLen); #if YKCS11_DBG == 1 - dump_hex(pSignature, *pulSignatureLen, stderr, CK_TRUE); + dump_data(pSignature, *pulSignatureLen, stderr, CK_TRUE, format_arg_hex); #endif if (!is_RSA_mechanism(op_info.mechanism.mechanism)) { @@ -1913,7 +1913,7 @@ CK_DEFINE_FUNCTION(CK_RV, C_Sign)( DBG("After removing DER encoding %lu", *pulSignatureLen); #if YKCS11_DBG == 1 - dump_hex(pSignature, *pulSignatureLen, stderr, CK_TRUE); + dump_data(pSignature, *pulSignatureLen, stderr, CK_TRUE, format_arg_hex); #endif }