diff --git a/doc/Attestation.adoc b/doc/Attestation.adoc new file mode 100644 index 0000000..818d457 --- /dev/null +++ b/doc/Attestation.adoc @@ -0,0 +1,20 @@ +Using Attestation +----------------- + +Attestation works through a special key slot called “f9” this comes +pre-loaded from factory with a key and cert signed by Yubico, but can be +overwritten. +After a key has been generated in a normal slot it can be attested by this +special key, this can be realised by using the yubico-piv-tool action attest: + + $ yubico-piv-tool --action=generate --slot=9a + ... + $ yubico-piv-tool --action=attest --slot=9a + +The output of this is a PEM encoded certificate, signed by the key in slot f9. There are a couple of special extensions on this certificate: + +* +1.3.6.1.4.1.41482.3.3+: Firmware version, encoded as 3 bytes, like: 040300 for 4.3.0 +* +1.3.6.1.4.1.41482.3.7+: Serial number, encoded as an integer. +* +1.3.6.1.4.1.41482.3.8+: Two bytes, the first encoding pin policy and the second touch policy +** Pin policy: 01 - never, 02 - once per session, 03 - always +** Touch policy: 01 - never, 02 - always, 03 - cached for 15s diff --git a/lib/ykpiv.c b/lib/ykpiv.c index 9081716..adcbb0c 100644 --- a/lib/ykpiv.c +++ b/lib/ykpiv.c @@ -873,7 +873,7 @@ ykpiv_rc ykpiv_import_private_key(ykpiv_state *state, const unsigned char key, u if (key == YKPIV_KEY_CARDMGM || key < YKPIV_KEY_RETIRED1 || (key > YKPIV_KEY_RETIRED20 && key < YKPIV_KEY_AUTHENTICATION) || - key > YKPIV_KEY_CARDAUTH) { + (key > YKPIV_KEY_CARDAUTH && key != YKPIV_KEY_ATTESTATION)) { return YKPIV_KEY_ERROR; } @@ -885,7 +885,8 @@ ykpiv_rc ykpiv_import_private_key(ykpiv_state *state, const unsigned char key, u if (touch_policy != YKPIV_TOUCHPOLICY_DEFAULT && touch_policy != YKPIV_TOUCHPOLICY_NEVER && - touch_policy != YKPIV_TOUCHPOLICY_ALWAYS) + touch_policy != YKPIV_TOUCHPOLICY_ALWAYS && + touch_policy != YKPIV_TOUCHPOLICY_CACHED) return YKPIV_GENERIC_ERROR; if (algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048) { diff --git a/lib/ykpiv.h b/lib/ykpiv.h index a89a827..f567d0e 100644 --- a/lib/ykpiv.h +++ b/lib/ykpiv.h @@ -141,6 +141,7 @@ extern "C" #define YKPIV_KEY_RETIRED18 0x93 #define YKPIV_KEY_RETIRED19 0x94 #define YKPIV_KEY_RETIRED20 0x95 +#define YKPIV_KEY_ATTESTATION 0xf9 #define YKPIV_OBJ_CAPABILITY 0x5fc107 #define YKPIV_OBJ_CHUID 0x5fc102 @@ -177,6 +178,8 @@ extern "C" #define YKPIV_OBJ_RETIRED19 0x5fc11f #define YKPIV_OBJ_RETIRED20 0x5fc120 +#define YKPIV_OBJ_ATTESTATION 0x5fff01 + #define YKPIV_INS_VERIFY 0x20 #define YKPIV_INS_CHANGE_REFERENCE 0x24 #define YKPIV_INS_RESET_RETRY 0x2c @@ -191,6 +194,7 @@ extern "C" #define YKPIV_INS_GET_VERSION 0xfd #define YKPIV_INS_RESET 0xfb #define YKPIV_INS_SET_PIN_RETRIES 0xfa +#define YKPIV_INS_ATTEST 0xf9 #define YKPIV_PINPOLICY_TAG 0xaa #define YKPIV_PINPOLICY_DEFAULT 0 @@ -202,6 +206,7 @@ extern "C" #define YKPIV_TOUCHPOLICY_DEFAULT 0 #define YKPIV_TOUCHPOLICY_NEVER 1 #define YKPIV_TOUCHPOLICY_ALWAYS 2 +#define YKPIV_TOUCHPOLICY_CACHED 3 #define YKPIV_IS_EC(a) ((a == YKPIV_ALGO_ECCP256 || a == YKPIV_ALGO_ECCP384)) #define YKPIV_IS_RSA(a) ((a == YKPIV_ALGO_RSA1024 || a == YKPIV_ALGO_RSA2048)) diff --git a/tool/cmdline.ggo b/tool/cmdline.ggo index c45e51c..76fda5f 100644 --- a/tool/cmdline.ggo +++ b/tool/cmdline.ggo @@ -33,11 +33,11 @@ option "action" a "Action to take" values="version","generate","set-mgm-key", "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","write-object", - "read-object" enum multiple + "read-object","attest" 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" -option "slot" s "What key slot to operate on" values="9a","9c","9d","9e","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f","90","91","92","93","94","95" enum optional +option "slot" s "What key slot to operate on" values="9a","9c","9d","9e","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f","90","91","92","93","94","95","f9" enum optional text " 9a is for PIV Authentication 9c is for Digital Signature (PIN always checked) @@ -62,7 +62,7 @@ option "valid-days" - "Time (in days) until the self-signed certificate expires" 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 "touch-policy" - "Set touch policy for action generate, import-key or set-mgm-key" values="never","always","cached" 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/util.c b/tool/util.c index 700b5e5..48434e3 100644 --- a/tool/util.c +++ b/tool/util.c @@ -330,6 +330,9 @@ int get_object_id(enum enum_slot slot) { case slot_arg_95: object = YKPIV_OBJ_RETIRED20; break; + case slot_arg_f9: + object = YKPIV_OBJ_ATTESTATION; + break; case slot__NULL: default: object = 0; @@ -601,6 +604,8 @@ unsigned char get_touch_policy(enum enum_touch_policy policy) { return YKPIV_TOUCHPOLICY_NEVER; case touch_policy_arg_always: return YKPIV_TOUCHPOLICY_ALWAYS; + case touch_policy_arg_cached: + return YKPIV_TOUCHPOLICY_CACHED; case touch_policy__NULL: default: return 0; diff --git a/tool/yubico-piv-tool.c b/tool/yubico-piv-tool.c index 93d4d8e..934d8d1 100644 --- a/tool/yubico-piv-tool.c +++ b/tool/yubico-piv-tool.c @@ -1651,6 +1651,68 @@ static bool list_readers(ykpiv_state *state) { return true; } +static bool attest(ykpiv_state *state, const char *slot, + enum enum_key_format key_format, const char *output_file_name) { + unsigned char data[2048]; + unsigned long len = sizeof(data); + bool ret = false; + X509 *x509 = NULL; + unsigned char templ[] = {0, YKPIV_INS_ATTEST, 0, 0}; + int key; + int sw; + FILE *output_file = open_file(output_file_name, OUTPUT); + if(!output_file) { + return false; + } + + sscanf(slot, "%2x", &key); + templ[2] = key; + + if(key_format != key_format_arg_PEM && key_format != key_format_arg_DER) { + fprintf(stderr, "Only PEM and DER format are supported for attest..\n"); + return false; + } + + if(ykpiv_transfer_data(state, templ, NULL, 0, data, &len, &sw) != YKPIV_OK) { + fprintf(stderr, "Failed to communicate.\n"); + goto attest_out; + } else if(sw != 0x9000) { + fprintf(stderr, "Failed to attest key.\n"); + goto attest_out; + } + + if(data[0] == 0x30) { + if(key_format == key_format_arg_PEM) { + const unsigned char *ptr = data; + int len2 = len; + x509 = X509_new(); + if(!x509) { + fprintf(stderr, "Failed allocating x509 structure.\n"); + goto attest_out; + } + x509 = d2i_X509(NULL, &ptr, len2); + if(!x509) { + fprintf(stderr, "Failed parsing x509 information.\n"); + goto attest_out; + } + PEM_write_X509(output_file, x509); + ret = true; + } else { + fwrite(data, len, 1, output_file); + } + ret = true; + } + +attest_out: + if(output_file != stdout) { + fclose(output_file); + } + if(x509) { + X509_free(x509); + } + return ret; +} + static bool write_object(ykpiv_state *state, int id, const char *input_file_name, int verbosity, enum enum_format format) { bool ret = false; @@ -1753,6 +1815,7 @@ int main(int argc, char *argv[]) { case action_arg_readMINUS_certificate: case action_arg_testMINUS_signature: case action_arg_testMINUS_decipher: + case action_arg_attest: 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]); @@ -1870,6 +1933,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_attest: case action_arg_readMINUS_object: case action__NULL: default: @@ -2060,6 +2124,12 @@ int main(int argc, char *argv[]) { ret = EXIT_FAILURE; } break; + case action_arg_attest: + if(attest(state, args_info.slot_orig, args_info.key_format_arg, + args_info.output_arg) == false) { + ret = EXIT_FAILURE; + } + break; case action__NULL: default: fprintf(stderr, "Wrong action. %d.\n", action);