/* * This file is part of the Pico HSM distribution (https://github.com/polhenarejos/pico-hsm). * Copyright (c) 2022 Pol Henarejos. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "file.h" #include "tusb.h" #include "hsm2040.h" #include "sc_hsm.h" #include "libopensc/card-sc-hsm.h" #include extern const uintptr_t end_data_pool; extern const uintptr_t start_data_pool; extern int flash_write_data_to_file(file_t *file, const uint8_t *data, uint16_t len); extern int flash_program_halfword (uintptr_t addr, uint16_t data); extern int flash_program_word (uintptr_t addr, uint32_t data); extern int flash_program_uintptr (uintptr_t addr, uintptr_t data); extern int flash_program_block(uintptr_t addr, const uint8_t *data, size_t len); extern uintptr_t flash_read_uintptr(uintptr_t addr); extern uint16_t flash_read_uint16(uintptr_t addr); extern uint8_t flash_read_uint8(uintptr_t addr); extern uint8_t *flash_read(uintptr_t addr); extern void low_flash_available(); //puts FCI in the RAPDU void process_fci(const file_t *pe) { uint8_t *p = res_APDU; uint8_t buf[64]; res_APDU_size = 0; res_APDU[res_APDU_size++] = 0x6f; res_APDU[res_APDU_size++] = 0x00; //computed later res_APDU[res_APDU_size++] = 0x81; res_APDU[res_APDU_size++] = 2; if (pe->data) { if ((pe->type & FILE_DATA_FUNC) == FILE_DATA_FUNC) { uint16_t len = ((int (*)(const file_t *, int))(pe->data))(pe, 0); res_APDU[res_APDU_size++] = (len >> 8) & 0xff; res_APDU[res_APDU_size++] = len & 0xff; } else { res_APDU[res_APDU_size++] = pe->data[1]; res_APDU[res_APDU_size++] = pe->data[0]; } } else { memset(res_APDU+res_APDU_size, 0, 2); res_APDU_size += 2; } res_APDU[res_APDU_size++] = 0x82; res_APDU[res_APDU_size++] = 1; res_APDU[res_APDU_size] = 0; if (pe->type == FILE_TYPE_INTERNAL_EF) res_APDU[res_APDU_size++] |= 0x08; else if (pe->type == FILE_TYPE_WORKING_EF) res_APDU[res_APDU_size++] |= pe->ef_structure & 0x7; else if (pe->type == FILE_TYPE_DF) res_APDU[res_APDU_size++] |= 0x38; res_APDU[res_APDU_size++] = 0x83; res_APDU[res_APDU_size++] = 2; put_uint16_t(pe->fid, res_APDU+res_APDU_size); res_APDU_size += 2; res_APDU[1] = res_APDU_size-2; } extern const uint8_t sc_hsm_aid[]; extern int parse_token_info(const file_t *f, int mode); extern int parse_cvca(const file_t *f, int mode); file_t file_entries[] = { /* 0 */ { .fid = 0x3f00 , .parent = 0xff, .name = NULL, .type = FILE_TYPE_DF, .data = NULL, .ef_structure = 0, .acl = {0} }, // MF /* 1 */ { .fid = 0x2f00 , .parent = 0, .name = NULL, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.DIR /* 2 */ { .fid = 0x2f01 , .parent = 0, .name = NULL, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.ATR /* 3 */ { .fid = 0x2f02 , .parent = 0, .name = NULL, .type = FILE_TYPE_WORKING_EF | FILE_DATA_FUNC,.data = (uint8_t *)parse_cvca, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.GDO /* 4 */ { .fid = 0x2f03 , .parent = 5, .name = NULL, .type = FILE_TYPE_WORKING_EF | FILE_DATA_FUNC,.data = (uint8_t *)parse_token_info, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.TokenInfo /* 5 */ { .fid = 0x5015 , .parent = 0, .name = NULL, .type = FILE_TYPE_DF, .data = NULL, .ef_structure = 0, .acl = {0} }, //DF.PKCS15 /* 6 */ { .fid = 0x5031 , .parent = 5, .name = NULL, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.ODF /* 7 */ { .fid = 0x5032 , .parent = 5, .name = NULL, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.TokenInfo /* 8 */ { .fid = 0x5033 , .parent = 0, .name = NULL, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.UnusedSpace /* 9 */ { .fid = 0x1081 , .parent = 5, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff} }, //PIN (PIN1) /* 10 */ { .fid = 0x1082 , .parent = 5, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff} }, //max retries PIN (PIN1) /* 11 */ { .fid = 0x1083 , .parent = 5, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff} }, //retries PIN (PIN1) /* 12 */ { .fid = 0x1088 , .parent = 5, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff} }, //PIN (SOPIN) /* 13 */ { .fid = 0x1089 , .parent = 5, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff} }, //max retries PIN (SOPIN) /* 14 */ { .fid = 0x108A , .parent = 5, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff} }, //retries PIN (SOPIN) /* 15 */ { .fid = EF_DKEK , .parent = 5, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff} }, //DKEK /* 16 */ { .fid = EF_DEVOPS , .parent = 5, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff} }, //Device options /* 17 */ { .fid = EF_PRKDFS , .parent = 5, .name = NULL, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.PrKDFs /* 18 */ { .fid = EF_PUKDFS , .parent = 5, .name = NULL, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.PuKDFs /* 19 */ { .fid = EF_CDFS , .parent = 5, .name = NULL, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.CDFs /* 20 */ { .fid = EF_AODFS , .parent = 5, .name = NULL, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.AODFs /* 21 */ { .fid = EF_DODFS , .parent = 5, .name = NULL, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.DODFs /* 22 */ { .fid = EF_SKDFS , .parent = 5, .name = NULL, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, //EF.SKDFs ///* 23 */ { .fid = 0x0000, .parent = 0, .name = openpgpcard_aid, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, /* 24 */ { .fid = 0x0000, .parent = 5, .name = sc_hsm_aid, .type = FILE_TYPE_WORKING_EF, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0} }, /* 25 */ { .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_UNKNOWN, .data = NULL, .ef_structure = 0, .acl = {0} } //end }; const file_t *MF = &file_entries[0]; const file_t *file_last = &file_entries[sizeof(file_entries)/sizeof(file_t)-1]; const file_t *file_openpgp = &file_entries[sizeof(file_entries)/sizeof(file_t)-3]; const file_t *file_sc_hsm = &file_entries[sizeof(file_entries)/sizeof(file_t)-2]; file_t *file_pin1 = NULL; file_t *file_retries_pin1 = NULL; file_t *file_sopin = NULL; file_t *file_retries_sopin = NULL; #define MAX_DYNAMIC_FILES 64 uint16_t dynamic_files = 0; file_t dynamic_file[MAX_DYNAMIC_FILES]; bool card_terminated = false; bool is_parent(const file_t *child, const file_t *parent) { if (child == parent) return true; if (child == MF) return false; return is_parent(&file_entries[child->parent], parent); } file_t *get_parent(file_t *f) { return &file_entries[f->parent]; } file_t *search_by_name(uint8_t *name, uint16_t namelen) { for (file_t *p = file_entries; p != file_last; p++) { if (p->name && *p->name == apdu.cmd_apdu_data_len && memcmp(p->name+1, name, namelen) == 0) { return p; } } return NULL; } file_t *search_by_fid(const uint16_t fid, const file_t *parent, const uint8_t sp) { for (file_t *p = file_entries; p != file_last; p++) { if (p->fid != 0x0000 && p->fid == fid) { if (!parent || (parent && is_parent(p, parent))) { if (!sp || sp == SPECIFY_ANY || (((sp & SPECIFY_EF) && (p->type & FILE_TYPE_INTERNAL_EF)) || ((sp & SPECIFY_DF) && p->type == FILE_TYPE_DF))) return p; } } } return NULL; } uint8_t make_path_buf(const file_t *pe, uint8_t *buf, uint8_t buflen, const file_t *top) { if (!buflen) return 0; if (pe == top) //MF or relative DF return 0; put_uint16_t(pe->fid, buf); return make_path_buf(&file_entries[pe->parent], buf+2, buflen-2, top)+2; } uint8_t make_path(const file_t *pe, const file_t *top, uint8_t *path) { uint8_t buf[MAX_DEPTH*2], *p = path; put_uint16_t(pe->fid, buf); uint8_t depth = make_path_buf(&file_entries[pe->parent], buf+2, sizeof(buf)-2, top)+2; for (int d = depth-2; d >= 0; d -= 2) { memcpy(p, buf+d, 2); p += 2; } return depth; } file_t *search_by_path(const uint8_t *pe_path, uint8_t pathlen, const file_t *parent) { uint8_t path[MAX_DEPTH*2]; if (pathlen > sizeof(path)) { return NULL; } for (file_t *p = file_entries; p != file_last; p++) { uint8_t depth = make_path(p, parent, path); if (pathlen == depth && memcmp(path, pe_path, depth) == 0) return p; } return NULL; } file_t *currentEF = NULL; file_t *currentDF = NULL; const file_t *selected_applet = NULL; bool isUserAuthenticated = false; bool authenticate_action(const file_t *ef, uint8_t op) { uint8_t acl = ef->acl[op]; if (acl == 0x0) return true; else if (acl == 0xff) return false; else if (acl == 0x90 || acl & 0x9F == 0x10) { // PIN required. if(isUserAuthenticated) { return true; } else { return false; } } return false; } #include "libopensc/pkcs15.h" void initialize_chain(file_chain_t **chain) { file_chain_t *next; for (file_chain_t *f = *chain; f; f = next) { next = f->next; free(f); } *chain = NULL; } void initialize_flash(bool hard) { if (hard) { const uint8_t empty[8] = { 0 }; flash_program_block(end_data_pool, empty, sizeof(empty)); low_flash_available(); } for (file_t *f = file_entries; f != file_last; f++) { if ((f->type & FILE_DATA_FLASH) == FILE_DATA_FLASH) f->data = NULL; } dynamic_files = 0; } void scan_flash() { initialize_flash(false); //soft initialization if (*(uintptr_t *)end_data_pool == 0xffffffff && *(uintptr_t *)(end_data_pool+sizeof(uintptr_t)) == 0xffffffff) { printf("First initialization (or corrupted!)\r\n"); const uint8_t empty[8] = { 0 }; flash_program_block(end_data_pool, empty, sizeof(empty)); //low_flash_available(); //wait_flash_finish(); } printf("SCAN\r\n"); uintptr_t base = flash_read_uintptr(end_data_pool); for (uintptr_t base = flash_read_uintptr(end_data_pool); base >= start_data_pool; base = flash_read_uintptr(base)) { if (base == 0x0) //all is empty break; uint16_t fid = flash_read_uint16(base+sizeof(uintptr_t)+sizeof(uintptr_t)); printf("[%x] scan fid %x, len %d\r\n",base,fid,flash_read_uint16(base+sizeof(uintptr_t)+sizeof(uintptr_t)+sizeof(uint16_t))); file_t *file = (file_t *)search_by_fid(fid, NULL, SPECIFY_EF); if (!file) { file = file_new(fid); if ((fid & 0xff00) == (KEY_PREFIX << 8)) { //add_file_to_chain(file, &ef_kf); } else if ((fid & 0xff00) == (PRKD_PREFIX << 8)) { //add_file_to_chain(file, &ef_prkdf); } else if ((fid & 0xff00) == (CD_PREFIX << 8)) { //add_file_to_chain(file, &ef_cdf); } else if ((fid & 0xff00) == (EE_CERTIFICATE_PREFIX << 8)) { //add_file_to_chain(file, &ef_pukdf); } else { TU_LOG1("SCAN FOUND ORPHAN FILE: %x\r\n",fid); continue; } } file->data = (uint8_t *)(base+sizeof(uintptr_t)+sizeof(uintptr_t)+sizeof(uint16_t)); if (flash_read_uintptr(base) == 0x0) { break; } } file_pin1 = search_by_fid(0x1081, NULL, SPECIFY_EF); if (file_pin1) { if (!file_pin1->data) { TU_LOG1("PIN1 is empty. Initializing with default password\r\n"); const uint8_t empty[33] = { 0 }; flash_write_data_to_file(file_pin1, empty, sizeof(empty)); } } else { TU_LOG1("FATAL ERROR: PIN1 not found in memory!\r\n"); } file_sopin = search_by_fid(0x1088, NULL, SPECIFY_EF); if (file_sopin) { if (!file_sopin->data) { TU_LOG1("SOPIN is empty. Initializing with default password\r\n"); const uint8_t empty[33] = { 0 }; flash_write_data_to_file(file_sopin, empty, sizeof(empty)); } } else { TU_LOG1("FATAL ERROR: SOPIN not found in memory!\r\n"); } file_retries_pin1 = search_by_fid(0x1083, NULL, SPECIFY_EF); if (file_retries_pin1) { if (!file_retries_pin1->data) { TU_LOG1("Retries PIN1 is empty. Initializing with default retriesr\n"); const uint8_t retries = 3; flash_write_data_to_file(file_retries_pin1, &retries, sizeof(uint8_t)); } } else { TU_LOG1("FATAL ERROR: Retries PIN1 not found in memory!\r\n"); } file_retries_sopin = search_by_fid(0x108A, NULL, SPECIFY_EF); if (file_retries_sopin) { if (!file_retries_sopin->data) { TU_LOG1("Retries SOPIN is empty. Initializing with default retries\r\n"); const uint8_t retries = 15; flash_write_data_to_file(file_retries_sopin, &retries, sizeof(uint8_t)); } } else { TU_LOG1("FATAL ERROR: Retries SOPIN not found in memory!\r\n"); } file_t *tf = NULL; tf = search_by_fid(0x1082, NULL, SPECIFY_EF); if (tf) { if (!tf->data) { TU_LOG1("Max retries PIN1 is empty. Initializing with default max retriesr\n"); const uint8_t retries = 3; flash_write_data_to_file(tf, &retries, sizeof(uint8_t)); } } else { TU_LOG1("FATAL ERROR: Max Retries PIN1 not found in memory!\r\n"); } tf = search_by_fid(0x1089, NULL, SPECIFY_EF); if (tf) { if (!tf->data) { TU_LOG1("Max Retries SOPIN is empty. Initializing with default max retries\r\n"); const uint8_t retries = 15; flash_write_data_to_file(tf, &retries, sizeof(uint8_t)); } } else { TU_LOG1("FATAL ERROR: Retries SOPIN not found in memory!\r\n"); } low_flash_available(); } uint8_t *file_read(const uint8_t *addr) { return flash_read((uintptr_t)addr); } uint16_t file_read_uint16(const uint8_t *addr) { return flash_read_uint16((uintptr_t)addr); } uint8_t file_read_uint8(const uint8_t *addr) { return flash_read_uint8((uintptr_t)addr); } file_t *search_dynamic_file(uint16_t fid) { for (int i = 0; i < dynamic_files; i++) { if (dynamic_file[i].fid == fid) return &dynamic_file[i]; } return NULL; } int delete_dynamic_file(file_t *f) { for (int i = 0; i < dynamic_files; i++) { if (dynamic_file[i].fid == f->fid) { for (int j = i+1; j < dynamic_files; j++) memcpy(&dynamic_file[j-1], &dynamic_file[j], sizeof(file_t)); dynamic_files--; return HSM_OK; } } return HSM_ERR_FILE_NOT_FOUND; } file_t *file_new(uint16_t fid) { file_t *f; if ((f = search_dynamic_file(fid))) return f; if (dynamic_files == MAX_DYNAMIC_FILES) return NULL; f = &dynamic_file[dynamic_files]; dynamic_files++; file_t file = { .fid = fid, .parent = 5, .name = NULL, .type = FILE_TYPE_WORKING_EF, .ef_structure = FILE_EF_TRANSPARENT, .data = NULL, .acl = {0} }; memcpy(f, &file, sizeof(file_t)); //memset((uint8_t *)f->acl, 0x90, sizeof(f->acl)); return f; } file_chain_t *add_file_to_chain(file_t *file, file_chain_t **chain) { if (search_file_chain(file->fid, *chain)) return NULL; file_chain_t *fc = (file_chain_t *)malloc(sizeof(file_chain_t)); fc->file = file; fc->next = *chain; *chain = fc; return fc; } file_t *search_file_chain(uint16_t fid, file_chain_t *chain) { for (file_chain_t *fc = chain; fc; fc = fc->next) { if (fid == fc->file->fid) { return fc->file; } } return NULL; }