added support for QES with German ID card

libnpa: requires an ID card that is initialized for QES. Today, this
means you need to register at sign-me.

virtualsmartcard: We don't actually create a real signature, because
PyCrypto can't handle ECDSA, let alone brainpoolP256r1. We only return a
64 byte buffer.
This commit is contained in:
Frank Morgner
2015-01-18 00:55:29 +01:00
parent ff71e301db
commit d89385c3e1
23 changed files with 555 additions and 424 deletions

View File

@@ -97,6 +97,14 @@ For Example: GivenNames=GERTRUD. \
The following Dataset Elements may be used in the dataset file: \
DocumentType, IssuingState, DateOfExpiry, GivenNames, FamilyNames, ReligiousArtisticName, AcademicTitle, DateOfBirth, PlaceOfBirth, Nationality, Sex, BirthName, Country, City, ZIP, Street, CommunityID, ResidencePermit1, ResidencePermit2, dg12, dg14, dg15, dg16, dg21.\
")
npa.add_argument("--esign-cert",
action="store",
type=argparse.FileType('rb'),
help="the card holder's certificate for QES")
npa.add_argument("--esign-ca-cert",
action="store",
type=argparse.FileType('rb'),
help="the CA's certificate for QES")
args = parser.parse_args()
@@ -107,6 +115,8 @@ ef_cardsecurity_data = None
ef_cardaccess_data = None
ca_key_data = None
cvca = None
esign_ca_cert = None
esign_cert = None
if (args.ef_cardsecurity):
ef_cardsecurity_data = args.ef_cardsecurity.read()
args.ef_cardsecurity.close()
@@ -119,6 +129,12 @@ if (args.ca_key):
if (args.cvca):
cvca = args.cvca.read()
args.cvca.close()
if (args.esign_cert):
esign_cert = args.esign_cert.read()
args.esign_cert.close()
if (args.esign_ca_cert):
esign_ca_cert = args.esign_ca_cert.read()
args.esign_ca_cert.close()
if not args.verbose:
logginglevel = logging.CRITICAL
@@ -139,5 +155,7 @@ else:
vicc = VirtualICC(args.file, 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, logginglevel=logginglevel)
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)
vicc.run()

View File

@@ -341,7 +341,31 @@ class CardGenerator(object):
eid.append(TransparentStructureEF(parent=eid, fid=0x0115, shortfid=0x15, data=dg21))
self.mf.append(eid)
self.sam = nPA_SAM(pin="111111", can="222222", mrz="IDD<<T220001293<<<<<<<<<<<<<<<6408125<2010315D<<<<<<<<<<<<<<MUSTERMANN<<ERIKA<<<<<<<<<<<<<", puk="3333333333", mf=self.mf)
# DF.CIA
cia=DF(parent=self.mf, fid=0xfffe, dfname='\xE8\x28\xBD\x08\x0F\xA0\x00\x00\x01\x67\x45\x53\x49\x47\x4E')
# EF.OD / EF.ODF
cia.append(TransparentStructureEF(parent=cia, fid=0x5031, shortfid=0x11, data='\xa0\x060\x04\x04\x02D\x00\xa4\x060\x04\x04\x02D\x04\xa8\x060\x04\x04\x02D\x08'))
# EF.CIAInfo / EF.TokenInfo
cia.append(TransparentStructureEF(parent=cia, fid=0x5032, shortfid=0x12, data='06\x02\x01\x01\x80\x11eSign Application\x03\x02\x06\xc0\xa2\x1a0\x18\x02\x01\x01\x02\x02\x10A\x05\x00\x03\x02\x06@\x06\t\x04\x00\x7f\x00\x07\x01\x01\x04\x01'))
# EF.PrKD / EF.PrKDF
cia.append(TransparentStructureEF(parent=cia, fid=0x4400, data='\xa080\x17\x0c\x0bPrK.ICC.QES\x03\x02\x07\x80\x04\x01\x01\x02\x01\x010\x15\x04\x01F\x03\x03\x06\x00@\x03\x02\x03\xb8\x02\x02\x00\x84\xa1\x03\x02\x01\x01\xa1\x060\x040\x02\x04\x00'))
# EF.PuKD / EF.PuKDF
cia.append(TransparentStructureEF(parent=cia, fid=0x4404, data='050!\x0c\x1fZertifikat des ZDA f\xc3\xbcr die QES0\x06\x04\x01E\x01\x01\xff\xa1\x080\x060\x04\x04\x02\xc0\x000:0&\x0c$Zertifikat des Inhabers f\xc3\xbcr die QES0\x06\x04\x01F\x01\x01\x00\xa1\x080\x060\x04\x04\x02\xc0\x01'))
# EF.AOD / EF.AODF
cia.append(TransparentStructureEF(parent=cia, fid=0x4408, data='0/0\x0f\x0c\teSign-PIN\x03\x02\x06@0\x03\x04\x01\x01\xa1\x170\x15\x03\x03\x02H\x1c\n\x01\x01\x02\x01\x06\x02\x01\x00\x80\x01\x810\x02\x04\x00'))
self.mf.append(cia)
# DF.eSign
esign=DF(parent=self.mf, fid=0xfffd, dfname='\xA0\x00\x00\x01\x67\x45\x53\x49\x47\x4E')
# ZDA certificate
esign.append(TransparentStructureEF(parent=esign, fid=0xC000, data=''))
# User's certificate
esign.append(TransparentStructureEF(parent=esign, fid=0xC001, data=''))
self.mf.append(esign)
self.sam = nPA_SAM(eid_pin="111111", can="222222", mrz="IDD<<T220001293<<<<<<<<<<<<<<<6408125<2010315D<<<<<<<<<<<<<<MUSTERMANN<<ERIKA<<<<<<<<<<<<<", puk="3333333333", qes_pin="444444", mf=self.mf)
# FIXME add CVCA for inspection systems and signature terminals. Here we only add the eID CVCA.
self.sam.current_SE.cvca = '\x7f\x21\x82\x01\xb6\x7f\x4e\x82\x01\x6e\x5f\x29\x01\x00\x42\x0e\x44\x45\x43\x56\x43\x41\x65\x49\x44\x30\x30\x31\x30\x32\x7f\x49\x82\x01\x1d\x06\x0a\x04\x00\x7f\x00\x07\x02\x02\x02\x02\x03\x81\x20\xa9\xfb\x57\xdb\xa1\xee\xa9\xbc\x3e\x66\x0a\x90\x9d\x83\x8d\x72\x6e\x3b\xf6\x23\xd5\x26\x20\x28\x20\x13\x48\x1d\x1f\x6e\x53\x77\x82\x20\x7d\x5a\x09\x75\xfc\x2c\x30\x57\xee\xf6\x75\x30\x41\x7a\xff\xe7\xfb\x80\x55\xc1\x26\xdc\x5c\x6c\xe9\x4a\x4b\x44\xf3\x30\xb5\xd9\x83\x20\x26\xdc\x5c\x6c\xe9\x4a\x4b\x44\xf3\x30\xb5\xd9\xbb\xd7\x7c\xbf\x95\x84\x16\x29\x5c\xf7\xe1\xce\x6b\xcc\xdc\x18\xff\x8c\x07\xb6\x84\x41\x04\x8b\xd2\xae\xb9\xcb\x7e\x57\xcb\x2c\x4b\x48\x2f\xfc\x81\xb7\xaf\xb9\xde\x27\xe1\xe3\xbd\x23\xc2\x3a\x44\x53\xbd\x9a\xce\x32\x62\x54\x7e\xf8\x35\xc3\xda\xc4\xfd\x97\xf8\x46\x1a\x14\x61\x1d\xc9\xc2\x77\x45\x13\x2d\xed\x8e\x54\x5c\x1d\x54\xc7\x2f\x04\x69\x97\x85\x20\xa9\xfb\x57\xdb\xa1\xee\xa9\xbc\x3e\x66\x0a\x90\x9d\x83\x8d\x71\x8c\x39\x7a\xa3\xb5\x61\xa6\xf7\x90\x1e\x0e\x82\x97\x48\x56\xa7\x86\x41\x04\x33\x47\xec\xf9\x6f\xfb\x4b\xd9\xb8\x55\x4e\xfb\xcc\xfc\x7d\x0b\x24\x2f\x10\x71\xe2\x9b\x4c\x9c\x62\x2c\x79\xe3\x39\xd8\x40\xaf\x67\xbe\xb9\xb9\x12\x69\x22\x65\xd9\xc1\x6c\x62\x57\x3f\x45\x79\xff\xd4\xde\x2d\xe9\x2b\xab\x40\x9d\xd5\xc5\xd4\x82\x44\xa9\xf7\x87\x01\x01\x5f\x20\x0e\x44\x45\x43\x56\x43\x41\x65\x49\x44\x30\x30\x31\x30\x32\x7f\x4c\x12\x06\x09\x04\x00\x7f\x00\x07\x03\x01\x02\x02\x53\x05\xfe\x0f\x01\xff\xff\x5f\x25\x06\x01\x00\x01\x00\x01\x08\x5f\x24\x06\x01\x03\x01\x00\x01\x08\x5f\x37\x40\x50\x67\x14\x5c\x68\xca\xe9\x52\x0f\x5b\xb3\x48\x17\xf1\xca\x9c\x43\x59\x3d\xb5\x64\x06\xc6\xa3\xb0\x06\xcb\xf3\xf3\x14\xe7\x34\x9a\xcf\x0c\xc6\xbf\xeb\xcb\xde\xfd\x10\xb4\xdc\xf0\xf2\x31\xda\x56\x97\x7d\x88\xf9\xf9\x01\x82\xd1\x99\x07\x6a\x56\x50\x64\x51'

View File

@@ -503,7 +503,7 @@ class RelayOS(SmartcardOS):
class NPAOS(Iso7816OS):
def __init__(self, mf, sam, ins2handler=None, maxle=MAX_EXTENDED_LE, ef_cardsecurity=None, ef_cardaccess=None, ca_key=None, cvca=None, disable_checks=False):
def __init__(self, mf, sam, ins2handler=None, maxle=MAX_EXTENDED_LE, ef_cardsecurity=None, ef_cardaccess=None, ca_key=None, cvca=None, disable_checks=False, esign_key=None, esign_ca_cert=None, esign_cert=None):
Iso7816OS.__init__(self, mf, sam, ins2handler, maxle)
self.ins2handler[0x86] = self.SAM.general_authenticate
self.ins2handler[0x2c] = self.SAM.reset_retry_counter
@@ -526,6 +526,13 @@ class NPAOS(Iso7816OS):
self.SAM.current_SE.cvca = cvca
if ca_key:
self.SAM.current_SE.ca_key = ca_key
esign = self.mf.select('dfname', '\xA0\x00\x00\x01\x67\x45\x53\x49\x47\x4E')
if esign_ca_cert:
ef = esign.select('fid', 0xC000)
ef.data = esign_ca_cert
if esign_cert:
ef = esign.select('fid', 0xC001)
ef.data = esign_cert
def formatResult(self, seekable, le, data, sw, sm):
@@ -642,7 +649,7 @@ class VirtualICC(object):
the vpcd, which forwards it to the application.
"""
def __init__(self, filename, datasetfile, card_type, host, port, readernum=None, ef_cardsecurity=None, ef_cardaccess=None, ca_key=None, cvca=None, disable_checks=False, logginglevel=logging.INFO):
def __init__(self, filename, 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):
from os.path import exists
logging.basicConfig(level = logginglevel,
@@ -674,7 +681,7 @@ class VirtualICC(object):
if card_type == "iso7816" or card_type == "ePass":
self.os = Iso7816OS(MF, SAM)
elif card_type == "nPA":
self.os = NPAOS(MF, SAM, ef_cardsecurity=ef_cardsecurity, ef_cardaccess=ef_cardaccess, ca_key=ca_key, cvca=cvca, disable_checks=disable_checks)
self.os = NPAOS(MF, SAM, ef_cardsecurity=ef_cardsecurity, ef_cardaccess=ef_cardaccess, ca_key=ca_key, cvca=cvca, disable_checks=disable_checks, esign_key=esign_key, esign_ca_cert=esign_ca_cert, esign_cert=esign_cert)
elif card_type == "cryptoflex":
self.os = CryptoflexOS(MF, SAM)
elif card_type == "relay":

View File

@@ -171,7 +171,7 @@ class nPA_SE(Security_Environment):
if self.sam.counter == 1 and not self.sam.active:
print "Must use CAN to activate"
return 0x63c1, ""
self.PACE_SEC = PACE_SEC(self.sam.PIN, eac.PACE_PIN)
self.PACE_SEC = PACE_SEC(self.sam.eid_pin, eac.PACE_PIN)
self.sam.counter -= 1
if self.sam.counter <= 1:
self.sam.active = False
@@ -372,6 +372,7 @@ class nPA_SE(Security_Environment):
if 1 != eac.TA_STEP6_verify(self.eac_ctx, self.at.iv, id_picc,
auxiliary_data, data):
eac.print_ossl_err()
print "Could not verify Terminal's signature"
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
print "Terminal's signature verified"
@@ -449,13 +450,18 @@ class nPA_SE(Security_Environment):
return sw, return_data
def compute_digital_signature(self, p1, p2, data):
# TODO Signing with brainpoolP256r1 or any other key needs some more effort ;-)
return '\x0D\xB2\x9B\xB9\x5E\x97\x7D\x42\x73\xCF\xA5\x45\xB7\xED\x5C\x39\x3F\xCE\xCD\x4A\xDE\xDC\x2B\x85\x23\x9F\x66\x52\x10\xC2\x67\xDC\xA6\x35\x94\x2D\x24\xED\xEB\xC8\x34\x6C\x4B\xD1\xA1\x15\xB4\x48\x3A\xA4\x4A\xCE\xFF\xED\x97\x0E\x07\xF3\x72\xF0\xFB\xA3\x62\x8C'
class nPA_SAM(SAM):
def __init__(self, pin, can, mrz, puk, mf, default_se = nPA_SE):
SAM.__init__(self, pin, None, mf)
def __init__(self, eid_pin, can, mrz, puk, qes_pin, mf, default_se = nPA_SE):
SAM.__init__(self, qes_pin, None, mf)
self.active = True
self.current_SE = default_se(self.mf, self)
self.eid_pin = eid_pin
self.can = can
self.mrz = mrz
self.puk = puk
@@ -479,8 +485,8 @@ class nPA_SAM(SAM):
# TODO allow terminals to change the PIN with permission "CAN allowed"
if not self.current_SE.at.keyref_is_pin():
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
self.PIN = data
print "Changed PIN to %r" % self.PIN
self.eid_pin = data
print "Changed PIN to %r" % self.eid_pin
else:
raise SwError(SW["ERR_DATANOTFOUND"])
elif p1 == 0x03:
@@ -528,26 +534,24 @@ class nPA_SAM(SAM):
return SW["NORMAL"], self.last_challenge
def verify(self, p1, p2, data):
if (p1 != 0x80 or p2 != 0x00):
raise SwError(SW["ERR_INCORRECTP1P2"])
if self.current_SE.eac_step == 6:
structure = unpack(data)
for tag, length, value in structure:
if tag == 6 and ALGO_MAPPING[value] == "DateOfExpiry":
# hell yes, this is a valid nPA
# TODO actually check it...
return SW["NORMAL"], ""
if tag == 6 and ALGO_MAPPING[value] == "DateOfBirth":
# hell yes, we are old enough
# TODO actually check it...
return SW["NORMAL"], ""
if tag == 6 and ALGO_MAPPING[value] == "CommunityID":
# well OK, we are living there
# TODO actually check it...
return SW["NORMAL"], ""
raise SwError(SW["WARN_NOINFO63"])
if p1 == 0x80 and p2 == 0x00:
if self.current_SE.eac_step == 6:
structure = unpack(data)
for tag, length, value in structure:
if tag == 6 and ALGO_MAPPING[value] == "DateOfExpiry":
# hell yes, this is a valid nPA
# TODO actually check it...
return SW["NORMAL"], ""
if tag == 6 and ALGO_MAPPING[value] == "DateOfBirth":
# hell yes, we are old enough
# TODO actually check it...
return SW["NORMAL"], ""
if tag == 6 and ALGO_MAPPING[value] == "CommunityID":
# well OK, we are living there
# TODO actually check it...
return SW["NORMAL"], ""
else:
return SAM.verify(self, p1, p2, data)
def parse_SM_CAPDU(self, CAPDU, header_authentication):
if hasattr(self.current_SE, "new_encryption_ctx"):