Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: support for Belgian eID cards #104

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,6 @@ before_script:
./configure OPENSC_LIBS="-L$PREFIX/lib -lopensc" || cat config.log;
fi

addons:
coverity_scan:
project:
name: "frankmorgner/vsmartcard"
description: "Umbrella project for various projects concerned with the emulation of different types of smart card readers or smart cards themselves"
notification_email: [email protected]
build_command: make -C $TRAVIS_BUILD_DIR/virtualsmartcard -C $TRAVIS_BUILD_DIR/ccid -C $TRAVIS_BUILD_DIR/pcsc-relay
branch_pattern: master

script:
# Build virtualsmartcard
- make -C $TRAVIS_BUILD_DIR/virtualsmartcard
Expand Down
1 change: 1 addition & 0 deletions virtualsmartcard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Currently the Virtual Smart Card supports the following types of smart cards:
(PACE, TA, CA)
- Electronic passport (ePass/MRTD) with support for BAC
- Cryptoflex smart card (incomplete)
- Belgian electronic ID card (WIP, this fork)

The vpcd is a smart card reader driver for [PCSC-Lite](http://pcsclite.alioth.debian.org/) and the windows smart
card service. It allows smart card applications to access the vpicc through
Expand Down
3 changes: 3 additions & 0 deletions virtualsmartcard/belpic-example-data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The .xml file in this directory was generated by [the scripts in the
eid-test-cards project](https://github.com/Fedict/eid-test-cards). We'll
make this look better at some point in the future, but not now.
82 changes: 82 additions & 0 deletions virtualsmartcard/belpic-example-data/belpic.xml

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion virtualsmartcard/src/vpicc/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ vpicccards_PYTHON = virtualsmartcard/cards/__init__.py \
virtualsmartcard/cards/ePass.py \
virtualsmartcard/cards/nPA.py \
virtualsmartcard/cards/Relay.py \
virtualsmartcard/cards/cryptoflex.py
virtualsmartcard/cards/cryptoflex.py \
virtualsmartcard/cards/belpic.py

do_subst = $(SED) \
-e 's,[@]PYTHON[@],$(PYTHON),g' \
Expand Down
5 changes: 3 additions & 2 deletions virtualsmartcard/src/vpicc/vicc.in
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Report bugs to @PACKAGE_BUGREPORT@

parser.add_argument("-t", "--type",
action="store",
choices=['iso7816', 'cryptoflex', 'ePass', 'nPA', 'relay', 'handler_test'],
choices=['iso7816', 'cryptoflex', 'ePass', 'nPA', 'relay', 'handler_test', 'belpic'],
default='iso7816',
help="type of smart card to emulate (default: %(default)s)")
parser.add_argument("-v", "--verbose",
Expand All @@ -58,6 +58,7 @@ parser.add_argument("-P", "--port",
parser.add_argument("-R", "--reversed",
action="store_true",
help="use reversed connection mode. vicc will wait for an incoming connection from vpcd. (default: %(default)s)")
parser.add_argument('--log-unparsed', action="store_true", help="Log unparsed command APDUs, rather than trying to parse them")
parser.add_argument('--version', action='version', version='%(prog)s @PACKAGE_VERSION@')

relay = parser.add_argument_group('Relaying a local smart card (`--type=relay`)')
Expand Down Expand Up @@ -156,7 +157,7 @@ vicc = VirtualICC(args.datasetfile, args.type, hostname, args.port,
readernum=args.reader, ef_cardaccess=ef_cardaccess_data,
ef_cardsecurity=ef_cardsecurity_data, ca_key=ca_key_data, cvca=cvca,
disable_checks=args.disable_ta_checks, esign_ca_cert=esign_ca_cert,
esign_cert=esign_cert, logginglevel=logginglevel)
esign_cert=esign_cert, logginglevel=logginglevel, logunparsed=args.log_unparsed)
try:
vicc.run()
except KeyboardInterrupt:
Expand Down
8 changes: 8 additions & 0 deletions virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,12 @@ def __generate_cryptoflex(self):
data="\x00\x00\x00\x01\x00\x01\x00\x00")) # EF.ICCSN
self.sam = CryptoflexSAM(self.mf)

def __generate_belpic(self):
from virtualsmartcard.cards.belpic import BelpicMF
self.__generate_iso_card()
self.mf = BelpicMF('belpic.xml')
self.sam.set_MF(self.mf)

def generateCard(self):
"""Generate a new card"""
if self.type == 'iso7816':
Expand All @@ -675,6 +681,8 @@ def generateCard(self):
self.__generate_cryptoflex()
elif self.type == 'nPA':
self.__generate_nPA()
elif self.type == 'belpic':
self.__generate_belpic()
else:
return (None, None)

Expand Down
13 changes: 11 additions & 2 deletions virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ def execute(self, msg):
"""
return ""

def logAPDU(self, parsed, unparsed):
if(self.logunparsed):
logging.info("Unparsed APDU:\n%s", hexdump(unparsed));
else:
logging.info("Parsed APDU:\n%s", str(parsed))

class Iso7816OS(SmartcardOS):

Expand Down Expand Up @@ -286,7 +291,7 @@ def notImplemented(*argz, **args):
return self.formatResult(False, 0, "",
SW["ERR_INCORRECTPARAMETERS"], False)

logging.info("Parsed APDU:\n%s", str(c))
self.logAPDU(parsed=c, unparsed=msg)

# Handle Class Byte
# {{{
Expand Down Expand Up @@ -391,7 +396,7 @@ def __init__(self, datasetfile, card_type, host, port,
readernum=None, ef_cardsecurity=None, ef_cardaccess=None,
ca_key=None, cvca=None, disable_checks=False, esign_key=None,
esign_ca_cert=None, esign_cert=None,
logginglevel=logging.INFO):
logginglevel=logging.INFO, logunparsed=False):
from os.path import exists

logging.basicConfig(level=logginglevel,
Expand Down Expand Up @@ -428,12 +433,16 @@ def __init__(self, datasetfile, card_type, host, port,
elif card_type == "handler_test":
from virtualsmartcard.cards.HandlerTest import HandlerTestOS
self.os = HandlerTestOS()
elif card_type == "belpic":
from virtualsmartcard.cards.belpic import BelpicOS
self.os = BelpicOS(MF, SAM)
else:
logging.warning("Unknown cardtype %s. Will use standard card_type \
(ISO 7816)", card_type)
card_type = "iso7816"
self.os = Iso7816OS(MF, SAM)
self.type = card_type
self.os.logunparsed = logunparsed

# Connect to the VPCD
self.host = host
Expand Down
9 changes: 8 additions & 1 deletion virtualsmartcard/src/vpicc/virtualsmartcard/cards/Relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import smartcard
import sys
from virtualsmartcard.VirtualSmartcard import SmartcardOS

from virtualsmartcard.utils import C_APDU

class RelayOS(SmartcardOS):
"""
Expand Down Expand Up @@ -113,6 +113,13 @@ def reset(self):
self.powerUp()

def execute(self, msg):
try:
c = C_APDU(msg)
self.logAPDU(parsed=c, unparsed=msg)
except ValueError as e:
# ignore the parse failure, just don't log the parsed APDU
logging.warning("Could not parse APDU:%s", str(e))

# sendCommandAPDU() expects a list of APDU bytes
apdu = map(ord, msg)

Expand Down
116 changes: 116 additions & 0 deletions virtualsmartcard/src/vpicc/virtualsmartcard/cards/belpic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#
# Copyright (C) 2017 Wouter Verhelst, Federal Public Service BOSA, DG DT
#
# This file is part of virtualsmartcard.
#
# virtualsmartcard is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.

from virtualsmartcard.VirtualSmartcard import Iso7816OS
from virtualsmartcard.ConstantDefinitions import MAX_SHORT_LE, FDB, LCB, REF
from virtualsmartcard.SmartcardFilesystem import MF, DF, TransparentStructureEF
from virtualsmartcard.SWutils import SW, SwError
from virtualsmartcard.utils import C_APDU, R_APDU

import logging

import xml.etree.ElementTree as ET

class BelpicOS(Iso7816OS):
def __init__(self, mf, sam, ins2handler=None, maxle=MAX_SHORT_LE):
Iso7816OS.__init__(self, mf, sam, ins2handler, maxle)
self.ins2handler = {
0xc0: self.getResponse,
0xa4: self.mf.selectFile,
0xb0: self.mf.readBinaryPlain,
0x20: self.SAM.verify,
0x24: self.SAM.change_reference_data,
0x22: self.SAM.manage_security_environment,
0x2a: self.SAM.perform_security_operation,
0xe4: self.getCardData,
0xe6: self.logOff
}
self.atr = '\x3B\x98\x13\x40\x0A\xA5\x03\x01\x01\x01\xAD\x13\x11'

# TODO: don't hardcode the value below, so that we can also emulate the v1.1 applet
def getCardData(self, p1, p2, data):
return SW["NORMAL"], "534C494E0123456789ABCDEF01234567F3360125011700030021010f".decode("hex")

# TODO: actually log off
def logOff(self, p1, p2, data):
return SW["NORMAL"]

def execute(self, msg):
c = C_APDU(msg)
if (c.ins == 0xa4 and c.p1 == 0x02):
# The belpic applet is a bit loose with interpretation of
# the ISO 7816 standard on the A4 command:
# - The MF can be selected by name from anywhere with P1 ==
# 0x02, rather than 0x00 as ISO 7816 requires
if (c.data == '3F00'.decode('hex')):
logging.info("Original APDU:\n%s\nRewritten to:\n", str(c))
c.p1 = 0
msg = c.render()
# - Child DFs can be selected with P1 == 0x02, rather than
# 0x01 as ISO 7816 requires
if (c.data == 'DF00'.decode('hex') or c.data == 'DF01'.decode('hex')):
logging.info("Original APDU:\n%s\nRewritten to:\n", str(c))
c.p1 = 1
msg = c.render()
return Iso7816OS.execute(self, msg)

def formatResult(self, seekable, le, data, sw, sm):
r = R_APDU(Iso7816OS.formatResult(self, seekable, le, data, sw, sm))
# The Belpic applet provides a bogus file length of 65536 for
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

# every file, and does not return an error or warning when the
# actual file length is shorter that the file as found; so
# filter out the EOFBEFORENEREAD warning
if (r.sw1 == 0x62 and r.sw2 == 0x82):
logging.info("Filtering out warning")
r.sw = "9000".decode("hex")
return r.render()

class BelpicMF(MF):
def __init__(self, datafile, filedescriptor=FDB["NOTSHAREABLEFILE" ] | FDB["DF"],
lifecycle=LCB["ACTIVATED"],simpletlv_data = None, bertlv_data = None):
MF.__init__(self, filedescriptor, lifecycle, simpletlv_data, bertlv_data, dfname = "A00000003029057000AD13100101FF".decode('hex'))
tree = ET.parse(datafile)
root = tree.getroot()
ns = {'f': 'urn:be:fedict:eid:dev:virtualcard:1.0'}
DF00 = DF(self, 0xDF00, dfname="A000000177504B43532D3135".decode('hex'))
self.append(DF00)
DF01 = DF(self, 0xDF01)
self.append(DF01)
parent = self
fid = 0
for f in root.findall('f:file', ns):
id = f.find('f:id', ns).text
content = f.find('f:content', ns).text
if content is not None:
if(len(id) == 12):
fid = int(id[8:], 16)
if (id[4:8] == 'DF00'):
parent = DF00
if (id[4:8] == 'DF01'):
parent = DF01
else:
fid = int(id[4:], 16)
parent.append(TransparentStructureEF(parent, fid, data = content.decode('hex')))

def select(self, attribute, value, reference=REF["IDENTIFIER_FIRST"], index_current=0):
if (hasattr(self, attribute) and
((getattr(self, attribute) == value) or
(attribute == 'dfname' and
getattr(self, attribute).startswith(value)))):
return self
return DF.select(self, attribute, value, reference, index_current)