diff --git a/lib/ykpiv.c b/lib/ykpiv.c index 18db705..a02edd8 100644 --- a/lib/ykpiv.c +++ b/lib/ykpiv.c @@ -498,11 +498,10 @@ ykpiv_rc ykpiv_hex_decode(const char *hex_in, size_t in_len, return YKPIV_OK; } -ykpiv_rc ykpiv_sign_data(ykpiv_state *state, +static ykpiv_rc _general_authenticate(ykpiv_state *state, const unsigned char *raw_in, size_t in_len, - unsigned char *sign_out, size_t *out_len, - unsigned char algorithm, unsigned char key) { - + unsigned char *out, size_t *out_len, + unsigned char algorithm, unsigned char key, bool decipher) { unsigned char indata[1024]; unsigned char *dataptr = indata; unsigned char data[1024]; @@ -522,14 +521,23 @@ ykpiv_rc ykpiv_sign_data(ykpiv_state *state, if(pad_len == 0) { pad_len = 256; } - if(in_len + RSA_PKCS1_PADDING_SIZE > pad_len) { - return YKPIV_SIZE_ERROR; + if(!decipher) { + if(in_len + RSA_PKCS1_PADDING_SIZE > pad_len) { + return YKPIV_SIZE_ERROR; + } + RSA_padding_add_PKCS1_type_1(sign_in, pad_len, raw_in, in_len); + in_len = pad_len; + } else { + if(in_len != pad_len) { + return YKPIV_SIZE_ERROR; + } + memcpy(sign_in, raw_in, in_len); } - RSA_padding_add_PKCS1_type_1(sign_in, pad_len, raw_in, in_len); - in_len = pad_len; break; case YKPIV_ALGO_ECCP256: - if(in_len > 32) { + if(!decipher && in_len > 32) { + return YKPIV_SIZE_ERROR; + } else if(decipher && in_len != 65) { return YKPIV_SIZE_ERROR; } memcpy(sign_in, raw_in, in_len); @@ -550,7 +558,7 @@ ykpiv_rc ykpiv_sign_data(ykpiv_state *state, dataptr += set_length(dataptr, in_len + bytes + 3); *dataptr++ = 0x82; *dataptr++ = 0x00; - *dataptr++ = 0x81; + *dataptr++ = algorithm == YKPIV_ALGO_ECCP256 && decipher ? 0x85 : 0x81; dataptr += set_length(dataptr, in_len); memcpy(dataptr, sign_in, (size_t)in_len); dataptr += in_len; @@ -592,10 +600,27 @@ ykpiv_rc ykpiv_sign_data(ykpiv_state *state, return YKPIV_SIZE_ERROR; } *out_len = len; - memcpy(sign_out, dataptr, len); + memcpy(out, dataptr, len); return YKPIV_OK; } +ykpiv_rc ykpiv_sign_data(ykpiv_state *state, + const unsigned char *raw_in, size_t in_len, + unsigned char *sign_out, size_t *out_len, + unsigned char algorithm, unsigned char key) { + + return _general_authenticate(state, raw_in, in_len, sign_out, out_len, + algorithm, key, false); +} + + +ykpiv_rc ykpiv_decipher_data(ykpiv_state *state, const unsigned char *in, + size_t in_len, unsigned char *out, size_t *out_len, + unsigned char algorithm, unsigned char key) { + return _general_authenticate(state, in, in_len, out, out_len, + algorithm, key, true); +} + ykpiv_rc ykpiv_get_version(ykpiv_state *state, char *version, size_t len) { APDU apdu; unsigned char data[0xff]; diff --git a/lib/ykpiv.h b/lib/ykpiv.h index b4051b5..26a4a93 100644 --- a/lib/ykpiv.h +++ b/lib/ykpiv.h @@ -75,6 +75,9 @@ extern "C" ykpiv_rc ykpiv_sign_data(ykpiv_state *state, const unsigned char *sign_in, size_t in_len,unsigned char *sign_out, size_t *out_len, unsigned char algorithm, unsigned char key); + ykpiv_rc ykpiv_decipher_data(ykpiv_state *state, const unsigned char *enc_in, + size_t in_len, unsigned char *enc_out, size_t *out_len, + unsigned char algorithm, unsigned char key); ykpiv_rc ykpiv_get_version(ykpiv_state *state, char *version, size_t len); ykpiv_rc ykpiv_verify(ykpiv_state *state, const char *pin, int *tries); ykpiv_rc ykpiv_fetch_object(ykpiv_state *state, int object_id, diff --git a/lib/ykpiv.map b/lib/ykpiv.map index 2b7a333..19969cc 100644 --- a/lib/ykpiv.map +++ b/lib/ykpiv.map @@ -47,3 +47,9 @@ global: local: *; }; + +YKPIV_0.2.0 +{ +global: + ykpiv_decipher_data; +} YKPIV_0.1.0; diff --git a/tool/cmdline.ggo b/tool/cmdline.ggo index 53b60fb..16fbff6 100644 --- a/tool/cmdline.ggo +++ b/tool/cmdline.ggo @@ -31,7 +31,7 @@ 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" enum multiple + "test-signature","test-decipher" 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" diff --git a/tool/yubico-piv-tool.c b/tool/yubico-piv-tool.c index c0385c2..ad8b1f8 100644 --- a/tool/yubico-piv-tool.c +++ b/tool/yubico-piv-tool.c @@ -1484,6 +1484,137 @@ test_out: return ret; } +static bool test_decipher(ykpiv_state *state, enum enum_slot slot, + const char *input_file_name, enum enum_key_format cert_format, int verbose) { + bool ret = false; + X509 *x509 = NULL; + EVP_PKEY *pubkey; + EC_KEY *tmpkey = NULL; + FILE *input_file = open_file(input_file_name, INPUT); + + if(!input_file) { + fprintf(stderr, "Failed opening input file %s.\n", input_file_name); + return false; + } + + if(isatty(fileno(input_file))) { + fprintf(stderr, "Please paste the certificate to encrypt for...\n"); + } + + if(cert_format == key_format_arg_PEM) { + x509 = PEM_read_X509(input_file, NULL, NULL, NULL); + } else if(cert_format == key_format_arg_DER) { + x509 = d2i_X509_fp(input_file, NULL); + } else { + fprintf(stderr, "Only PEM or DER format is supported for test-decipher.\n"); + goto decipher_out; + } + if(!x509) { + fprintf(stderr, "Failed loading certificate for test-decipher.\n"); + goto decipher_out; + } + + { + int key = 0; + unsigned char algorithm; + + pubkey = X509_get_pubkey(x509); + if(!pubkey) { + fprintf(stderr, "Parse error.\n"); + goto decipher_out; + } + algorithm = get_algorithm(pubkey); + if(algorithm == 0) { + goto decipher_out; + } + sscanf(cmdline_parser_slot_values[slot], "%2x", &key); + if(algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048) { + unsigned char secret[32]; + unsigned char secret2[32]; + unsigned char data[256]; + int len; + size_t len2 = sizeof(data); + RSA *rsa = EVP_PKEY_get1_RSA(pubkey); + + if(RAND_pseudo_bytes(secret, sizeof(secret)) == -1) { + fprintf(stderr, "error: no randomness.\n"); + ret = false; + goto decipher_out; + } + + RSA_padding_add_PKCS1_type_1(data, RSA_size(rsa), secret, sizeof(secret)); + len = RSA_public_encrypt(RSA_size(rsa), data, data, rsa, RSA_NO_PADDING); + if(len < 0) { + fprintf(stderr, "Failed performing RSA encryption!\n"); + goto decipher_out; + } + if(ykpiv_decipher_data(state, data, (size_t)len, data, &len2, algorithm, key) != YKPIV_OK) { + fprintf(stderr, "RSA decrypt failed!\n"); + goto decipher_out; + } + /* for some reason we have to give the padding check function data + 1 */ + len = RSA_padding_check_PKCS1_type_1(secret2, sizeof(secret2), data + 1, len2 - 1, RSA_size(rsa)); + if(len == sizeof(secret)) { + if(verbose) { + fprintf(stderr, "Generated nonce: "); + dump_hex(secret, sizeof(secret), stderr, true); + fprintf(stderr, "Decrypted nonce: "); + dump_hex(secret2, sizeof(secret2), stderr, true); + } + if(memcmp(secret, secret2, sizeof(secret)) == 0) { + fprintf(stderr, "Successfully performed RSA decryption!\n"); + ret = true; + } else { + fprintf(stderr, "Failed performing RSA decryption!\n"); + } + } else { + fprintf(stderr, "Failed unwrapping PKCS1 envelope.\n"); + } + } else { + unsigned char secret[32]; + unsigned char secret2[32]; + unsigned char public_key[65]; + unsigned char *ptr = public_key; + size_t len = sizeof(secret); + EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pubkey); + + tmpkey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + EC_KEY_generate_key(tmpkey); + ECDH_compute_key(secret, len, EC_KEY_get0_public_key(ec), tmpkey, NULL); + + i2o_ECPublicKey(tmpkey, &ptr); + if(ykpiv_decipher_data(state, public_key, sizeof(public_key), secret2, &len, algorithm, key) != YKPIV_OK) { + fprintf(stderr, "Failed ECDH exchange!\n"); + goto decipher_out; + } + if(verbose) { + fprintf(stderr, "ECDH host generated: "); + dump_hex(secret, len, stderr, true); + fprintf(stderr, "ECDH card generated: "); + dump_hex(secret2, len, stderr, true); + } + if(memcmp(secret, secret2, 32) == 0) { + fprintf(stderr, "Successfully performed ECDH exchange with card.\n"); + ret = true; + } else { + fprintf(stderr, "ECDH exchange with card failed!\n"); + } + } + } + +decipher_out: + if(tmpkey) { + EC_KEY_free(tmpkey); + } + if(x509) { + X509_free(x509); + } + if(input_file != stdin) { + fclose(input_file); + } + return ret; +} + int main(int argc, char *argv[]) { struct gengetopt_args_info args_info; ykpiv_state *state; @@ -1514,6 +1645,7 @@ int main(int argc, char *argv[]) { case action_arg_deleteMINUS_certificate: case action_arg_readMINUS_certificate: case action_arg_testMINUS_signature: + case action_arg_testMINUS_decipher: if(args_info.slot_arg == slot__NULL) { fprintf(stderr, "The '%s' action needs a slot (-s) to operate on.\n", cmdline_parser_action_values[action]); @@ -1596,6 +1728,7 @@ int main(int argc, char *argv[]) { case action_arg_readMINUS_certificate: case action_arg_status: case action_arg_testMINUS_signature: + case action_arg_testMINUS_decipher: case action__NULL: default: if(verbosity) { @@ -1758,6 +1891,12 @@ int main(int argc, char *argv[]) { ret = EXIT_FAILURE; } break; + case action_arg_testMINUS_decipher: + if(test_decipher(state, args_info.slot_arg, args_info.input_arg, + args_info.key_format_arg, verbosity) == false) { + ret = EXIT_FAILURE; + } + break; case action__NULL: default: fprintf(stderr, "Wrong action. %d.\n", action);