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 Portuguese Electronic ID Cards #208

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
9 changes: 9 additions & 0 deletions pteid/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Portuguese Electronic Identity Cards

Basic support for PTEID cards is added.
The cards cannot be used with the standard PKI and tools as the objects are signed by a self generated CA.
The Object structure, permissions and certificates tries to follow the same structure as original cards.

This is useful for development purposes, especially when using the PKCS11 interface.
Some objects may be inconsistent as they were copied from a development card. Further developments will focus on properly emulating PTEID cards.

398 changes: 398 additions & 0 deletions pteid/generate_pteid_card.py

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,97 @@ def __generate_cryptoflex(self):
data=b"\x00\x00\x00\x01\x00\x01\x00\x00")) # EF.ICCSN
self.sam = CryptoflexSAM(self.mf)

def __generate_PTEID(self):
def ___get_fs_entry(data, fid):

obj = data.get(fid, {})
fci = obj.get('fci', b'')

if len(fci) != 0:
fci = a2b_base64(fci)

name = obj.get('name', b'')
if len(name) != 0:
name = a2b_base64(name)

fdata = obj.get('data', b'')
if len(fdata) != 0:
fdata = a2b_base64(fdata)

return name, fci, fdata

"""Generate the Filesystem and SAM of a cryptoflex card"""
from virtualsmartcard.cards.PTEID import PTEID_MF
from virtualsmartcard.cards.PTEID import PTEID_SAM
import json
from binascii import a2b_base64
logging.info("Opening card.json")

f = open('card.json', 'r')
data = json.loads(f.read())
f.close()

self.mf = PTEID_MF()
name, fci, fdata = ___get_fs_entry(data, '3f00-0001')
fk = DF(parent=self.mf, fid=0x0001,
dfname=name,
extra_fci_data=fci)
self.mf.append(fk)

name, fci, fdata = ___get_fs_entry(data, '3f00-4f01')
info_a = DF(parent=self.mf, fid=0x4f01,
dfname=name,
extra_fci_data=fci)

self.mf.append(info_a)

# OK

# EF DIR
for fid in [0x2f00, 0x0003]:
path = '3f00-%04x' % (fid,)
name, fci, fdata = ___get_fs_entry(data, path)
self.mf.append(TransparentStructureEF(parent=self.mf, fid=fid,
data=fdata,
extra_fci_data=fci))

# TRACE


# ADF CIA PKI
name, fci, fdata = ___get_fs_entry(data, '3f00-4f00')
adf_cia = DF(parent=self.mf, fid=0x4F00,
dfname=name,
extra_fci_data=fci)

for fid in [0x5031, 0x5032]:
path = '3f00-4f00-%04x' % (fid, )
name, fci, fdata = ___get_fs_entry(data, path)
adf_cia.append(TransparentStructureEF(parent=adf_cia, fid=fid,
data=fdata,
extra_fci_data=fci))

self.mf.append(adf_cia)

# ADF PKI
name, fci, fdata = ___get_fs_entry(data, '3f00-5f00')
adf = DF(parent=self.mf, fid=0x5f00, dfname=name, extra_fci_data=fci)

for fid in [0x4401, 0xef0c, 0xEF02,
0xEF05, 0xEF06, 0xEF07,
0xEF08, 0xEF09, 0xEF0D,
0xEF0E, 0xEF0F, 0xEF10,
0xEF11, 0xEF12]:
path = '3f00-5f00-%04x' % (fid, )
name, fci, fdata = ___get_fs_entry(data, path)
adf.append(TransparentStructureEF(parent=adf, fid=fid,
data=fdata,
extra_fci_data=fci))

self.mf.append(adf)
private_key = binascii.a2b_base64(data.get('auth-private-key', {}).get('data', None))
self.sam = PTEID_SAM(self.mf, private_key=private_key)

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

Expand Down
13 changes: 7 additions & 6 deletions virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from virtualsmartcard.utils import inttostring, stringtoint, C_APDU
from virtualsmartcard.SWutils import SwError, SW
import virtualsmartcard.CryptoUtils as vsCrypto

import logging

class ControlReferenceTemplate:
"""
Expand All @@ -35,7 +35,7 @@ class ControlReferenceTemplate:
Reference Template:
HT, AT, KT, CCT, DST, CT-sym, CT-asym.
"""
def __init__(self, tag, config=""):
def __init__(self, tag, config=b""):
"""
Generates a new CRT

Expand All @@ -62,7 +62,7 @@ def __init__(self, tag, config=""):
self.algorithm = None
self.blocklength = None
self.usage_qualifier = None
if config != "":
if config != b'':
self.parse_SE_config(config)
self.__config_string = config

Expand Down Expand Up @@ -101,10 +101,10 @@ def __set_algo(self, data):
:param data: reference to an algorithm
"""

if data not in ALGO_MAPPING:
if data[0] not in ALGO_MAPPING:
Copy link
Owner

Choose a reason for hiding this comment

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

I believe you're using the first byte of a binary string here, right?

I think we need to change ALGO_MAPPING to map binary strings to names of algorithms instead of mapping a byte (integer) to algorithms (i.e. we need to change . In nPA.py, for example, you can see a mapping from an OID (binary string) to some algorithm....

diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/ConstantDefinitions.py b/virtualsmartcard/src/vpicc/virtualsmartcard/ConstantDefinitions.py
index d81e221..c2ebf90 100644
--- a/virtualsmartcard/src/vpicc/virtualsmartcard/ConstantDefinitions.py
+++ b/virtualsmartcard/src/vpicc/virtualsmartcard/ConstantDefinitions.py
@@ -215,20 +215,20 @@ CRT_TEMPLATE["CT"] = 0xB8  # Template for Confidentiality
 
 # This table maps algorithms to numbers. It is used in the security environment
 ALGO_MAPPING = {}
-ALGO_MAPPING[0x00] = "DES3-ECB"
-ALGO_MAPPING[0x01] = "DES3-CBC"
-ALGO_MAPPING[0x02] = "AES-ECB"
-ALGO_MAPPING[0x03] = "AES-CBC"
-ALGO_MAPPING[0x04] = "DES-ECB"
-ALGO_MAPPING[0x05] = "DES-CBC"
-ALGO_MAPPING[0x06] = "MD5"
-ALGO_MAPPING[0x07] = "SHA"
-ALGO_MAPPING[0x08] = "SHA256"
-ALGO_MAPPING[0x09] = "MAC"
-ALGO_MAPPING[0x0A] = "HMAC"
-ALGO_MAPPING[0x0B] = "CC"
-ALGO_MAPPING[0x0C] = "RSA"
-ALGO_MAPPING[0x0D] = "DSA"
+ALGO_MAPPING[b'\x00'] = "DES3-ECB"
+ALGO_MAPPING[b'\x01'] = "DES3-CBC"
+ALGO_MAPPING[b'\x02'] = "AES-ECB"
+ALGO_MAPPING[b'\x03'] = "AES-CBC"
+ALGO_MAPPING[b'\x04'] = "DES-ECB"
+ALGO_MAPPING[b'\x05'] = "DES-CBC"
+ALGO_MAPPING[b'\x06'] = "MD5"
+ALGO_MAPPING[b'\x07'] = "SHA"
+ALGO_MAPPING[b'\x08'] = "SHA256"
+ALGO_MAPPING[b'\x09'] = "MAC"
+ALGO_MAPPING[b'\x0A'] = "HMAC"
+ALGO_MAPPING[b'\x0B'] = "CC"
+ALGO_MAPPING[b'\x0C'] = "RSA"
+ALGO_MAPPING[b'\x0D'] = "DSA"

Copy link
Author

Choose a reason for hiding this comment

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

Corrected as you suggested.

raise SwError(SW["ERR_REFNOTUSABLE"])
else:
self.algorithm = ALGO_MAPPING[data]
self.algorithm = ALGO_MAPPING[data[0]]
self.__replace_tag(0x80, data)

def __set_key(self, tag, value):
Expand Down Expand Up @@ -149,14 +149,15 @@ def __replace_tag(self, tag, data):
replace it. Otherwise append tag, length and value to the config
string.
"""

position = 0
while position < len(self.__config_string) and \
self.__config_string[position] != tag:
length = stringtoint(self.__config_string[position+1])
position += length + 3

if position < len(self.__config_string): # Replace Tag
length = stringtoint(self.__config_string[position+1])
length = stringtoint(chr(self.__config_string[position+1]))
self.__config_string = self.__config_string[:position] +\
inttostring(tag) + inttostring(len(data)) + data +\
self.__config_string[position+2+length:]
Expand Down
60 changes: 37 additions & 23 deletions virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ def make_property(prop, doc):
lambda self: delattr(self, "_"+prop),
doc)


class File(object):
"""Template class for a smartcard file."""
bertlv_data = make_property("bertlv_data", "list of (tag, length, "
Expand Down Expand Up @@ -412,19 +411,20 @@ class DF(File):
"""Class for a dedicated file"""
data = make_property("data", "unknown")
content = make_property("content", "list of files of the DF")
dfname = make_property("dfname", "string with up to 16 bytes. DF name,"
"which can also be used as application"
"identifier.")
dfname = b"" # make_property("dfname", "string with up to 16 bytes. DF name,"
# "which can also be used as application"
# "identifier.")
Copy link
Owner

Choose a reason for hiding this comment

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

why comment this out?


def __init__(self, parent, fid,
filedescriptor=FDB["NOTSHAREABLEFILE"] | FDB["DF"],
lifecycle=LCB["ACTIVATED"],
simpletlv_data=None, bertlv_data=None, dfname=None, data=""):
simpletlv_data=None, bertlv_data=None, dfname=None, data="",
extra_fci_data=b''):
"""
See File for more.
"""
File.__init__(self, parent, fid, filedescriptor, lifecycle,
simpletlv_data, bertlv_data)
simpletlv_data, bertlv_data, None, extra_fci_data)
if dfname:
if len(dfname) > 16:
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
Expand Down Expand Up @@ -515,10 +515,10 @@ def select(self, attribute, value, reference=REF["IDENTIFIER_FIRST"],
# not found
if isinstance(value, int):
logging.debug("file (%s=%x) not found in:\n%s" %
(attribute, value, self))
(attribute, value, self.fid))
elif isinstance(value, str):
logging.debug("file (%s=%r) not found in:\n%s" %
(attribute, value, self))
(attribute, value, self.fid))
raise SwError(SW["ERR_FILENOTFOUND"])

def remove(self, file):
Expand Down Expand Up @@ -679,30 +679,43 @@ def _selectFile(self, p1, p2, data):
P1_PATH_FROM_MF = 0x08
P1_PATH_FROM_CURRENTDF = 0x09

if (p1 >> 4) != 0 or p1 == P1_FILE:
#if (p1 >> 4) != 0 or
if p1 == P1_FILE:
import binascii
logging.debug(f"p1 >> 4 or P1_FILE: {binascii.hexlify(data)}")
# RFU OR
# When P1='00', the card knows either because of a specific coding
# of the file identifier or because of the context of execution of
# the command if the file to select is the MF, a DF or an EF.
if data[:2] == inttostring(self.fid):
selected = walk(self, data[2:])
elif data[:2] == inttostring(self.currentDF().fid):
selected = walk(self.currentDF(), data[2:])
else:
selected = walk(self.currentDF(), data)
try:
if data[:2] == inttostring(self.fid):
selected = walk(self, data[2:])
elif data[:2] == inttostring(self.currentDF().fid):
selected = walk(self.currentDF(), data[2:])
else:
selected = walk(self.currentDF(), data)
except SwError as e:
# If everything fails, look at MF
selected = walk(self, data)
Copy link
Owner

Choose a reason for hiding this comment

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

technically speaking, this is an error, because the application specifically did not do the selection from MF; typically, there is no fall back directory, just the error is returned...


elif p1 == P1_CHILD_DF or p1 == P1_CHILD_EF:
logging.debug("P1_CHILD_DF or P1_CHILD_EF")
selected = self.currentDF().select('fid', stringtoint(data))
if ((p1 == P1_CHILD_DF and not isinstance(selected, DF)) or
(p1 == P1_CHILD_EF and not isinstance(selected, EF))):
# Command incompatible with file structure
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
elif p1 == P1_PATH_FROM_MF:
logging.debug("P1 PATH FROM MF")
selected = walk(self, data)
elif p1 == P1_PATH_FROM_CURRENTDF:
logging.debug("P1 PATH FROM CURRENT DF")
selected = walk(self.currentDF(), data)
elif p1 == P1_PARENT_DF:
logging.debug("P1 PARENT DF")
selected = self.current.parent
elif p1 == P1_DF_NAME:
logging.debug("P1 DF NAME")
df = self.currentDF()
if df == self or df not in self.content:
index_current = -1
Expand Down Expand Up @@ -748,7 +761,6 @@ def selectFile(self, p1, p2, data):
data = bertlv_pack([(tag, len(fdm), fdm)])

self.current = file
logging.info("Selected %s" % file)

return SW["NORMAL"], data

Expand All @@ -761,8 +773,8 @@ def dataUnitsDecodePlain(self, p1, p2, data):
list of data strings.
"""
if p1 >> 7:
# If bit 1 of INS is set to 0 and bit 8 of P1 to 1, then bits 7
# and 6 of P1 are set to 00 (RFU), bits 5 to 1 of P1 encode a
# If bit 8 of INS is set to 1, then bits 7
# and 6 of P1 are set to 00 (RFU), bits 3 to 1 of P1 encode a
# short EF identifier and P2 (eight bits) encodes an offset
# from zero to 255.
ef = self.currentDF().select('shortfid', p1 & 0x1f)
Expand Down Expand Up @@ -817,8 +829,8 @@ def readBinaryPlain(self, p1, p2, data):
data as binary string.
"""
ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data)
result = ef.readbinary(offsets[0])

result = ef.readbinary(offsets[0])
return SW["NORMAL"], result

def readBinaryEncapsulated(self, p1, p2, data):
Expand Down Expand Up @@ -1423,7 +1435,8 @@ class EF(File):
def __init__(self, parent, fid, filedescriptor,
lifecycle=LCB["ACTIVATED"],
simpletlv_data=None, bertlv_data=None,
datacoding=DCB["ONETIMEWRITE"], shortfid=0):
datacoding=DCB["ONETIMEWRITE"], shortfid=0,
extra_fci_data=b''):
"""
The constructor is supposed to be involved creation of a by creation of
a TransparentStructureEF or RecordStructureEF.
Expand All @@ -1439,7 +1452,7 @@ def __init__(self, parent, fid, filedescriptor,
self.shortfid = shortfid
File.__init__(self, parent, fid, filedescriptor, lifecycle,
simpletlv_data,
bertlv_data)
bertlv_data, extra_fci_data=extra_fci_data)
self.datacoding = datacoding


Expand All @@ -1451,14 +1464,15 @@ def __init__(self, parent, fid,
filedescriptor=FDB["EFSTRUCTURE_TRANSPARENT"],
lifecycle=LCB["ACTIVATED"],
simpletlv_data=None, bertlv_data=None,
datacoding=DCB["ONETIMEWRITE"], shortfid=0, data=""):
datacoding=DCB["ONETIMEWRITE"], shortfid=0, data="",
extra_fci_data=b''):
"""
See EF for more.
"""
EF.__init__(self, parent, fid,
filedescriptor, lifecycle,
simpletlv_data, bertlv_data,
datacoding, shortfid)
datacoding, shortfid, extra_fci_data)
self.data = data

def readbinary(self, offset):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,9 @@ def __init__(self, datasetfile, card_type, host, port,
elif card_type == "cryptoflex":
from virtualsmartcard.cards.cryptoflex import CryptoflexOS
self.os = CryptoflexOS(MF, SAM)
elif card_type == "PTEID":
from virtualsmartcard.cards.PTEID import PTEIDOS
self.os = PTEIDOS(MF, SAM)
elif card_type == "relay":
from virtualsmartcard.cards.Relay import RelayOS
from virtualsmartcard.cards.RelayMiddleman import RelayMiddleman
Expand Down
Loading