From 295c523e4e174a4adef7cae6e0463b1164e52c75 Mon Sep 17 00:00:00 2001 From: William Roberts Date: Tue, 8 Jul 2014 13:52:48 -0700 Subject: [PATCH] Add AES support for PIV General Authenticate This adds algorithm IDs 0xA, 0xA, 0xC which as documented by the NIST PIV specification is algorithms AES-128, AES-192 and AES-256 respectively. This patch also addresses some of the hardcodes that prevented nonces greater than the single byte TLV length tags would allow. It was explicitly tested with AES-256 and 256 byte nonces. Signed-off-by: William Roberts --- src/libopensc/card-piv.c | 344 ++++++++++++++++++++++++++++++--------- src/libopensc/opensc.h | 27 +-- src/libopensc/sc.c | 18 ++ 3 files changed, 296 insertions(+), 93 deletions(-) diff --git a/src/libopensc/card-piv.c b/src/libopensc/card-piv.c index f063c1b0..017cdcaf 100644 --- a/src/libopensc/card-piv.c +++ b/src/libopensc/card-piv.c @@ -1367,21 +1367,58 @@ static int piv_write_binary(sc_card_t *card, unsigned int idx, } /* - * Card initialization is not standard. - * Some cards use mutual or external authentication using s 3des key. We - * will read in the key from a file. + * Card initialization is NOT standard. + * Some cards use mutual or external authentication usings 3des or aes key. We + * will read in the key from a file either binary or hex encided. * This is only needed during initialization/personalization of the card */ -static int piv_get_3des_key(sc_card_t *card, u8 *key) +#ifdef ENABLE_OPENSSL +static const EVP_CIPHER *get_cipher_for_algo(int alg_id) +{ + switch (alg_id) { + case 0x0: return EVP_des_ede3_ecb(); + case 0x1: return EVP_des_ede3_ecb(); /* 2TDES */ + case 0x3: return EVP_des_ede3_ecb(); + case 0x8: return EVP_aes_128_ecb(); + case 0xA: return EVP_aes_192_ecb(); + case 0xC: return EVP_aes_256_ecb(); + default: return NULL; + } +} +#endif + +static int get_keylen(unsigned int alg_id, size_t *size) +{ + switch(alg_id) { + case 0x01: *size = 192/8; /* 2TDES still has 3 single des keys phase out by 12/31/2010 */ + break; + case 0x00: + case 0x03: *size = 192/8; + break; + case 0x08: *size = 128/8; + break; + case 0x0A: *size = 192/8; + break; + case 0x0C: *size = 256/8; + break; + default: + return SC_ERROR_INVALID_ARGUMENTS; + } + return SC_SUCCESS; +} + +static int piv_get_key(sc_card_t *card, unsigned int alg_id, u8 **key, size_t *len) { int r; - int f = -1; - char keybuf[24*3]; /* 3des key as three sets of xx:xx:xx:xx:xx:xx:xx:xx - * with a : between which is 71 bytes */ + size_t fsize; + FILE *f = NULL; char * keyfilename = NULL; - size_t outlen; + size_t expected_keylen; + size_t keylen; + u8 * keybuf = NULL; + u8 * tkey = NULL; SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE); @@ -1389,38 +1426,82 @@ static int piv_get_3des_key(sc_card_t *card, u8 *key) if (keyfilename == NULL) { sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL, - "Unable to get PIV_EXT_AUTH_KEY=filename for general_external_authenticate\n"); + "Unable to get PIV_EXT_AUTH_KEY=(null) for general_external_authenticate\n"); r = SC_ERROR_FILE_NOT_FOUND; goto err; } - if ((f = open(keyfilename, O_RDONLY)) < 0) { - sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL," Unable to load 3des key for general_external_authenticate\n"); + + r = get_keylen(alg_id, &expected_keylen); + if(r) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Invalid cipher selector, none found for: %02x\n", alg_id); + r = SC_ERROR_INVALID_ARGUMENTS; + goto err; + } + + f = fopen(keyfilename, "rb"); + if (!f) { + sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL," Unable to load key from file\n"); r = SC_ERROR_FILE_NOT_FOUND; goto err; } - if (read(f, keybuf, 71) != 71) { - sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL," Unable to read 3des key for general_external_authenticate\n"); + + fseek(f, 0L, SEEK_END); + fsize = ftell(f); + fseek(f, 0L, SEEK_SET); + + keybuf = malloc(fsize+1); /* if not binary, need null to make it a string */ + if (!keybuf) { + sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL," Unable to allocate key memory"); + r = SC_ERROR_OUT_OF_MEMORY; + goto err; + } + keybuf[fsize] = 0x00; /* incase it is text need null */ + + if (fread(keybuf, 1, fsize, f) != fsize) { + sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL," Unable to read key\n"); r = SC_ERROR_WRONG_LENGTH; goto err; } - keybuf[23] = '\0'; - keybuf[47] = '\0'; - keybuf[71] = '\0'; - outlen = 8; - r = sc_hex_to_bin(keybuf, key, &outlen); - if (r) goto err; - outlen = 8; - r = sc_hex_to_bin(keybuf+24, key+8, &outlen); - if (r) goto err; - outlen = 8; - r = sc_hex_to_bin(keybuf+48, key+16, &outlen); - if (r) goto err; + + tkey = malloc(expected_keylen); + if (!tkey) { + sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL," Unable to allocate key memory"); + r = SC_ERROR_OUT_OF_MEMORY; + goto err; + } + + if (fsize == expected_keylen) { /* it must be binary */ + memcpy(tkey, keybuf, expected_keylen); + } else { + /* if the key-length is larger then binary length, we assume hex encoded */ + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Treating key as hex-encoded!\n"); + sc_right_trim(keybuf, fsize); + keylen = expected_keylen; + r = sc_hex_to_bin((char *)keybuf, tkey, &keylen); + if (keylen !=expected_keylen || r != 0 ) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Error formatting key\n"); + if (r == 0) + r = SC_ERROR_INCOMPATIBLE_KEY; + goto err; + } + } + *key = tkey; + tkey = NULL; + *len = expected_keylen; + r = SC_SUCCESS; err: - if (f >=0) - close(f); + if (f) + fclose(f); + if (keybuf) { + free(keybuf); + } + if (tkey) { + free(tkey); + } SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, r); + return r; } /* @@ -1440,26 +1521,28 @@ static int piv_general_mutual_authenticate(sc_card_t *card, u8 *rbuf = NULL; size_t rbuflen; u8 nonce[8] = {0xDE, 0xE0, 0xDE, 0xE1, 0xDE, 0xE2, 0xDE, 0xE3}; - u8 sbuf[255], key[24]; + u8 sbuf[255]; u8 *p, *q; + u8 *key = NULL; + size_t keylen; EVP_CIPHER_CTX ctx; const EVP_CIPHER *cipher; SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE); EVP_CIPHER_CTX_init(&ctx); - - switch (alg_id) { - case 1: cipher=EVP_des_ede3_ecb(); break; - case 2: cipher=EVP_des_ede3_cbc(); break; - case 3: cipher=EVP_des_ede3_ecb(); break; - case 4: cipher=EVP_des_ede3_cbc(); break; - default: cipher=EVP_des_ede3_ecb(); break; + cipher = get_cipher_for_algo(alg_id); + if(!cipher) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Invalid cipher selector, none found for: %02x\n", alg_id); + r = SC_ERROR_INVALID_ARGUMENTS; + goto err; } - r = piv_get_3des_key(card, key); - if (r != SC_SUCCESS) + r = piv_get_key(card, alg_id, &key, &keylen); + if (r) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Error geting General Auth key\n"); goto err; + } r = sc_lock(card); if (r != SC_SUCCESS) @@ -1578,19 +1661,30 @@ err: SC_FUNC_RETURN(card->ctx, SC_LOG_DEBUG_NORMAL, r); } + /* Currently only used for card administration */ static int piv_general_external_authenticate(sc_card_t *card, unsigned int key_ref, unsigned int alg_id) { int r; #ifdef ENABLE_OPENSSL - int outl, outl2; - int N; + int tmplen; + int outlen; int locked = 0; - u8 *rbuf = NULL; + u8 *p; + u8 *rbuf = NULL; + u8 *key = NULL; + u8 *cypher_text = NULL; + u8 *output_buf = NULL; + const u8 *body = NULL; + const u8 *challenge_data = NULL; size_t rbuflen; - u8 sbuf[255], key[24]; - u8 *p, *q; + size_t body_len; + size_t output_len; + size_t challenge_len; + size_t keylen = 0; + size_t cypher_text_len = 0; + u8 sbuf[255]; EVP_CIPHER_CTX ctx; const EVP_CIPHER *cipher; @@ -1598,17 +1692,20 @@ static int piv_general_external_authenticate(sc_card_t *card, EVP_CIPHER_CTX_init(&ctx); - switch (alg_id) { - case 1: cipher=EVP_des_ede3_ecb(); break; - case 2: cipher=EVP_des_ede3_cbc(); break; - case 3: cipher=EVP_des_ede3_ecb(); break; - case 4: cipher=EVP_des_ede3_cbc(); break; - default: cipher=EVP_des_ede3_ecb(); break; + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Selected cipher for algorithm id: %02x\n", alg_id); + + cipher = get_cipher_for_algo(alg_id); + if(!cipher) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Invalid cipher selector, none found for: %02x\n", alg_id); + r = SC_ERROR_INVALID_ARGUMENTS; + goto err; } - r = piv_get_3des_key(card, key); - if (r != SC_SUCCESS) + r = piv_get_key(card, alg_id, &key, &keylen); + if (r) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Error geting General Auth key\n"); goto err; + } r = sc_lock(card); if (r != SC_SUCCESS) @@ -1623,53 +1720,140 @@ static int piv_general_external_authenticate(sc_card_t *card, /* get a challenge */ r = piv_general_io(card, 0x87, alg_id, key_ref, sbuf, p - sbuf, &rbuf, &rbuflen); + if (r < 0) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Error getting Challenge\n"); + goto err; + } - if (r < 0) goto err; - q = rbuf; - if ( (*q++ != 0x7C) - || (*q++ != rbuflen - 2) - || (*q++ != 0x81) - || (*q++ != rbuflen - 4)) { + /* + * the value here corresponds with the response size, so we use this + * to alloc the response buffer, rather than re-computing it. + */ + output_len = r; + + /* Remove the encompassing outer TLV of 0x7C and get the data */ + body = sc_asn1_find_tag(card->ctx, rbuf, + r, 0x7C, &body_len); + if (!body) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Invalid Challenge Data response of NULL\n"); r = SC_ERROR_INVALID_DATA; goto err; } - /* assuming challenge and response are same size i.e. des3 */ - p = sbuf; - *p++ = 0x7c; - *p++ = *(rbuf + 1); - *p++ = 0x82; - *p++ = *(rbuf + 3); - N = *(rbuf + 3); /* assuming 2 * N + 6 < 128 */ + /* Get the challenge data indicated by the TAG 0x81 */ + challenge_data = sc_asn1_find_tag(card->ctx, body, + body_len, 0x81, &challenge_len); + if (!challenge_data) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Invalid Challenge Data none found in TLV\n"); + r = SC_ERROR_INVALID_DATA; + goto err; + } + /* Store this to sanity check that plaintext length and cyphertext lengths match */ + /* TODO is this required */ + tmplen = challenge_len; + + /* Encrypt the challenge with the secret */ if (!EVP_EncryptInit(&ctx, cipher, key, NULL)) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Encrypt fail\n"); r = SC_ERROR_INTERNAL; goto err; } - EVP_CIPHER_CTX_set_padding(&ctx,0); - if (!EVP_EncryptUpdate(&ctx, p, &outl, q, N)) { - r = SC_ERROR_INTERNAL; - goto err; - } - if(!EVP_EncryptFinal(&ctx, p+outl, &outl2)) { - r = SC_ERROR_INTERNAL; - goto err; - } - if (outl+outl2 != N) { - r = SC_ERROR_INTERNAL; - goto err; - } - p += N; - r = piv_general_io(card, 0x87, alg_id, key_ref, sbuf, p - sbuf, NULL, NULL); + cypher_text = malloc(challenge_len); + if (!cypher_text) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not allocate buffer for cipher text\n"); + r = SC_ERROR_INTERNAL; + goto err; + } + + EVP_CIPHER_CTX_set_padding(&ctx,0); + if (!EVP_EncryptUpdate(&ctx, cypher_text, &outlen, challenge_data, challenge_len)) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Encrypt update fail\n"); + r = SC_ERROR_INTERNAL; + goto err; + } + cypher_text_len += outlen; + + if (!EVP_EncryptFinal(&ctx, cypher_text + cypher_text_len, &outlen)) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Final fail\n"); + r = SC_ERROR_INTERNAL; + goto err; + } + cypher_text_len += outlen; + + /* + * Actually perform the sanity check on lengths plaintext length vs + * encrypted length + */ + if (cypher_text_len != (size_t)tmplen) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Length test fail\n"); + r = SC_ERROR_INTERNAL; + goto err; + } + + output_buf = malloc(output_len); + if(!output_buf) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not allocate output buffer: %s\n", + strerror(errno)); + r = SC_ERROR_INTERNAL; + goto err; + } + + p = output_buf; + + /* + * Build: 7C[82] + * Start off by capturing the data of the response: + * - 82 + * Build the outside TLV (7C) + * Advance past that tag + len + * Build the body (82) + * memcopy the body past the 7C portion + * Transmit + */ + tmplen = put_tag_and_len(0x82, cypher_text_len, NULL); + + tmplen = put_tag_and_len(0x7C, tmplen, &p); + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Tmplen2: %zd\n", tmplen); + + /* Build the 0x82 TLV and append to the 7C tag */ + tmplen += put_tag_and_len(0x82, cypher_text_len, &p); + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Tmplen1: %zd\n", tmplen); + memcpy(p, cypher_text, cypher_text_len); + p += cypher_text_len; + tmplen += cypher_text_len; + + /* Sanity check the lengths again */ + if(output_len != (size_t)tmplen) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Allocated and computed lengths do not match! " + "Expected %zd, found: %d\n", output_len, tmplen); + r = SC_ERROR_INTERNAL; + goto err; + } + + r = piv_general_io(card, 0x87, alg_id, key_ref, output_buf, output_len, NULL, NULL); + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Got response challenge\n"); err: + EVP_CIPHER_CTX_cleanup(&ctx); + if (locked) sc_unlock(card); - EVP_CIPHER_CTX_cleanup(&ctx); - sc_mem_clear(key, sizeof(key)); + + if (key) { + sc_mem_clear(key, keylen); + free(key); + } + if (rbuf) free(rbuf); + + if (cypher_text) + free(cypher_text); + + if (output_buf) + free(output_buf); #else sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL,"OpenSSL Required"); r = SC_ERROR_NOT_SUPPORTED; diff --git a/src/libopensc/opensc.h b/src/libopensc/opensc.h index d250fe91..5df97687 100644 --- a/src/libopensc/opensc.h +++ b/src/libopensc/opensc.h @@ -354,7 +354,7 @@ struct sc_pin_cmd_data { #define PACE_PIN_ID_CAN 0x02 #define PACE_PIN_ID_PIN 0x03 #define PACE_PIN_ID_PUK 0x04 -/** +/** * Input data for EstablishPACEChannel() */ struct establish_pace_channel_input { @@ -377,7 +377,7 @@ struct establish_pace_channel_input { const unsigned char *certificate_description; }; -/** +/** * Output data for EstablishPACEChannel() */ struct establish_pace_channel_output { @@ -821,12 +821,12 @@ sc_reader_t *sc_ctx_get_reader(sc_context_t *ctx, unsigned int i); * * @param ctx pointer to a sc_context_t * @param pcsc_context_handle pointer to the new context_handle to use - * @param pcsc_card_handle pointer to the new card_handle to use + * @param pcsc_card_handle pointer to the new card_handle to use * @return SC_SUCCESS on success and an error code otherwise. */ int sc_ctx_use_reader(sc_context_t *ctx, void * pcsc_context_handle, void * pcsc_card_handle); -/** +/** * Returns a pointer to the specified sc_reader_t object * @param ctx OpenSC context * @param name name of the reader to look for @@ -835,7 +835,7 @@ int sc_ctx_use_reader(sc_context_t *ctx, void * pcsc_context_handle, void * pcsc */ sc_reader_t *sc_ctx_get_reader_by_name(sc_context_t *ctx, const char *name); -/** +/** * Returns a pointer to the specified sc_reader_t object * @param ctx OpenSC context * @param id id of the reader (starting from 0) @@ -911,7 +911,7 @@ int sc_detect_card_presence(sc_reader_t *reader); * @retval = 1 if the timeout occured */ int sc_wait_for_event(sc_context_t *ctx, unsigned int event_mask, - sc_reader_t **event_reader, unsigned int *event, + sc_reader_t **event_reader, unsigned int *event, int timeout, void **reader_states); /** @@ -980,8 +980,8 @@ int sc_read_binary(struct sc_card *card, unsigned int idx, u8 * buf, size_t count, unsigned long flags); /** * Write data to a binary EF - * @param card struct sc_card object on which to issue the command - * @param idx index within the file for the data to be written + * @param card struct sc_card object on which to issue the command + * @param idx index within the file for the data to be written * @param buf buffer with the data * @param count number of bytes to write * @param flags flags for the WRITE BINARY command (currently not used) @@ -1151,8 +1151,8 @@ int sc_file_set_content(sc_file_t *file, const u8 *content, * Sets the content of a sc_path_t object. * @param path sc_path_t object to set * @param type type of path - * @param id value of the path - * @param id_len length of the path value + * @param id value of the path + * @param id_len length of the path value * @param index index within the file * @param count number of bytes * @return SC_SUCCESS on success and an error code otherwise @@ -1163,7 +1163,7 @@ int sc_path_set(sc_path_t *path, int type, const u8 *id, size_t id_len, void sc_format_path(const char *path_in, sc_path_t *path_out); /** * Return string representation of the given sc_path_t object - * Warning: as static memory is used for the return value + * Warning: as static memory is used for the return value * this function is not thread-safe !!! * @param path sc_path_t object of the path to be printed * @return pointer to a const buffer with the string representation @@ -1179,7 +1179,7 @@ const char *sc_print_path(const sc_path_t *path); */ int sc_path_print(char *buf, size_t buflen, const sc_path_t *path); /** - * Compares two sc_path_t objects + * Compares two sc_path_t objects * @param patha sc_path_t object of the first path * @param pathb sc_path_t object of the second path * @return 1 if both paths are equal and 0 otherwise @@ -1203,7 +1203,7 @@ int sc_concatenate_path(sc_path_t *d, const sc_path_t *p1, const sc_path_t *p2); */ int sc_append_path(sc_path_t *dest, const sc_path_t *src); /** - * Checks whether one path is a prefix of another path + * Checks whether one path is a prefix of another path * @param prefix sc_path_t object with the prefix * @param path sc_path_t object with the path which should start * with the given prefix @@ -1224,6 +1224,7 @@ const sc_path_t *sc_get_mf_path(void); int sc_hex_to_bin(const char *in, u8 *out, size_t *outlen); int sc_bin_to_hex(const u8 *, size_t, char *, size_t, int separator); +size_t sc_right_trim(u8 *buf, size_t len); scconf_block *sc_get_conf_block(sc_context_t *ctx, const char *name1, const char *name2, int priority); /** diff --git a/src/libopensc/sc.c b/src/libopensc/sc.c index 027b2aba..1cc03109 100644 --- a/src/libopensc/sc.c +++ b/src/libopensc/sc.c @@ -111,6 +111,24 @@ int sc_bin_to_hex(const u8 *in, size_t in_len, char *out, size_t out_len, return 0; } +/* + * Right trim all non-printable characters + */ +size_t sc_right_trim(u8 *buf, size_t len) { + + size_t i; + + for(i=len-1; i >=0; i--) { + if(!isprint(buf[i])) { + buf[i] = '\0'; + len--; + continue; + } + break; + } + return len; +} + u8 *ulong2bebytes(u8 *buf, unsigned long x) { if (buf != NULL) {