Refactoring: Moving card specific stuff to separate package

git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@499 96b47cad-a561-4643-ad3b-153ac7d7599c
This commit is contained in:
oepen
2011-08-23 10:10:15 +00:00
parent fd7b98d65b
commit ad398b5d64
7 changed files with 277 additions and 220 deletions

View File

@@ -3,6 +3,7 @@ CLEANFILES = $(bin_SCRIPTS)
EXTRA_DIST = vicc.in
vpiccdir = $(pythondir)/virtualsmartcard
vpicccardsdir = $(vpiccdir)/cards
vpicc_PYTHON = virtualsmartcard/CardGenerator.py \
virtualsmartcard/SEutils.py \
@@ -16,6 +17,10 @@ vpicc_PYTHON = virtualsmartcard/CardGenerator.py \
virtualsmartcard/VirtualSmartcard.py \
virtualsmartcard/__init__.py
vpicccards_PYTHON = virtualsmartcard/cards/__init__.py \
virtualsmartcard/cards/ePass.py \
virtualsmartcard/cards/cryptoflex.py
do_subst = sed -e 's,[@]PYTHON[@],$(PYTHON),g'
vicc: vicc.in Makefile

View File

@@ -25,6 +25,7 @@ from virtualsmartcard.SmartcardFilesystem import MF, DF, TransparentStructureEF
from virtualsmartcard.ConstantDefinitions import FDB
from virtualsmartcard.CryptoUtils import protect_string, read_protected_string
import virtualsmartcard.SmartcardSAM
from virtualsmartcard.cards import ePass, cryptoflex
# pgp directory
#self.mf.append(DF(parent=self.mf,
@@ -127,7 +128,7 @@ class CardGenerator(object):
mf.append(df)
self.mf = mf
self.sam = virtualsmartcard.SmartcardSAM.PassportSAM(self.mf)
self.sam = ePass.PassportSAM(self.mf)
def __generate_cryptoflex(self):
from virtualsmartcard.SmartcardFilesystem import CryptoflexMF

View File

@@ -690,87 +690,4 @@ class Security_Environment(object):
return SW["NORMAL"], result
else:
#FIXME: Where to put the keys?
return SW["NORMAL"], ""
#}}}
class CryptoflexSE(Security_Environment):
def __init__(self, mf):
Security_Environment.__init__(self, mf)
def generate_public_key_pair(self, p1, p2, data):
"""
In the Cryptoflex card this command only supports RSA keys.
@param data: Contains the public exponent used for key generation
@param p1: The keynumber. Can be used later to refer to the generated key
@param p2: Used to specify the keylength.
The mapping is: 0x40 => 256 Bit, 0x60 => 512 Bit, 0x80 => 1024
"""
from Crypto.PublicKey import RSA
from Crypto.Util.randpool import RandomPool
keynumber = p1 #TODO: Check if key exists
keylength_dict = {0x40: 256, 0x60: 512, 0x80: 1024}
if not keylength_dict.has_key(p2):
raise SwError(SW["ERR_INCORRECTP1P2"])
else:
keylength = keylength_dict[p2]
rnd = RandomPool()
PublicKey = RSA.generate(keylength, rnd.get_bytes)
self.dst.key = PublicKey
e_in = struct.unpack("<i", data)
if e_in[0] != 65537:
logging.warning("Warning: Exponents different from 65537 are ignored!" +\
"The Exponent given is %i" % e_in[0])
#Encode Public key
n = PublicKey.__getstate__()['n']
n_str = inttostring(n)
n_str = n_str[::-1]
e = PublicKey.__getstate__()['e']
e_str = inttostring(e, 4)
e_str = e_str[::-1]
pad = 187 * '\x30' #We don't have CRT components, so we need to pad
pk_n = TLVutils.bertlv_pack(((0x81, len(n_str), n_str),
(0x01, len(pad), pad),
(0x82, len(e_str), e_str)))
#Private key
d = PublicKey.__getstate__()['d']
#Write result to FID 10 12 EF-PUB-KEY
df = self.mf.currentDF()
ef_pub_key = df.select("fid", 0x1012)
ef_pub_key.writebinary([0], [pk_n])
data = ef_pub_key.getenc('data')
#Write private key to FID 00 12 EF-PRI-KEY (not necessary?)
#How to encode the private key?
ef_priv_key = df.select("fid", 0x0012)
ef_priv_key.writebinary([0], [inttostring(d)])
data = ef_priv_key.getenc('data')
return PublicKey
class ePass_SE(Security_Environment):
def __init__(self, MF, SE, ssc=None):
self.ssc = ssc
Security_Environment.__init__(self, MF, SE)
def compute_cryptographic_checksum(self, p1, p2, data):
"""
Compute a cryptographic checksum (e.g. MAC) for the given data.
Algorithm and key are specified in the current (CAPDU) SE. The ePass
uses a Send Sequence Counter for MAC calculation
"""
if p1 != 0x8E or p2 != 0x80:
raise SwError(SW["ERR_INCORRECTP1P2"])
self.ssc += 1
checksum = vsCrypto.crypto_checksum(self.cct.algorithm, self.cct.key,
data, self.cct.iv, self.ssc)
return SW["NORMAL"], checksum
return SW["NORMAL"], ""

View File

@@ -17,14 +17,14 @@
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
import struct, hashlib, logging
import logging
from pickle import dumps, loads
from os import urandom
import virtualsmartcard.CryptoUtils as vsCrypto
from virtualsmartcard.SWutils import SwError, SW
from virtualsmartcard.utils import inttostring, stringtoint
from virtualsmartcard.SEutils import Security_Environment, CryptoflexSE, ePass_SE
from virtualsmartcard.SEutils import Security_Environment
def get_referenced_cipher(p1):
"""
@@ -336,139 +336,7 @@ class SAM(object):
def manage_security_environment(self, p1, p2, data):
return self.current_SE.manage_security_environment(p1, p2, data)
class PassportSAM(SAM):
"""
SAM for ICAO ePassport. Implements Basic access control and key derivation
for Secure Messaging.
"""
def __init__(self, mf):
import virtualsmartcard.SmartcardFilesystem as vsFS
ef_dg1 = vsFS.walk(mf, "\x00\x04\x01\x01")
dg1 = ef_dg1.readbinary(5)
self.mrz1 = dg1[:43]
self.mrz2 = dg1[44:]
self.KSeed = None
self.KEnc = None
self.KMac = None
self.KSenc = None
self.KSmac = None
self.__computeKeys()
SAM.__init__(self, None, None, mf)
self.current_SE = ePass_SE(mf, None, None)
self.current_SE.cct.algorithm = "CC"
self.current_SE.ct.algorithm = "DES3-CBC"
def __computeKeys(self):
"""
Computes the keys depending on the machine readable
zone of the passport according to TR-PKI mrtds ICC read-only
access v1.1 annex E.1.
"""
MRZ_information = self.mrz2[0:10] + self.mrz2[13:20] + self.mrz2[21:28]
H = hashlib.sha1(MRZ_information).digest()
self.KSeed = H[:16]
self.KEnc = self.derive_key(self.KSeed, 1)
self.KMac = self.derive_key(self.KSeed, 2)
def derive_key(self, seed, c):
"""
Derive a key according to TR-PKI mrtds ICC read-only access v1.1
annex E.1.
c is either 1 for encryption or 2 for MAC computation.
Returns: Ka + Kb
Note: Does not adjust parity. Nobody uses that anyway ..."""
D = seed + struct.pack(">i", c)
H = hashlib.sha1(D).digest()
Ka = H[0:8]
Kb = H[8:16]
return Ka + Kb
def external_authenticate(self, p1, p2, resp_data):
"""Performs the basic access control protocol as defined in
the ICAO MRTD standard"""
rnd_icc = self.last_challenge
#Receive Mutual Authenticate APDU from terminal
#Decrypt data and check MAC
Eifd = resp_data[:-8]
Mifd = self._mac(self.KMac, Eifd)
#Check the MAC
if not Mifd == resp_data[-8:]:
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
#Decrypt the data
plain = vsCrypto.decrypt("DES3-CBC", self.KEnc, resp_data[:-8])
#Split decrypted data into the two nonces and
if plain[8:16] != rnd_icc:
raise SwError(SW["WARN_NOINFO63"])
#Extraxt keying material from IFD, generate ICC keying material
Kifd = plain[16:]
rnd_ifd = plain[:8]
Kicc = urandom(16)
#Generate Answer
data = plain[8:16] + plain[:8] + Kicc
Eicc = vsCrypto.encrypt("DES3-CBC", self.KEnc, data)
Micc = self._mac(self.KMac, Eicc)
#Derive the final keys
KSseed = vsCrypto.operation_on_string(Kicc, Kifd, lambda a, b: a^b)
self.KSenc = self.derive_key(KSseed, 1)
self.KSmac = self.derive_key(KSseed, 2)
#self.ssc = rnd_icc[-4:] + rnd_ifd[-4:]
#Set the current SE
self.current_SE.ct.key = self.KSenc
self.current_SE.cct.key = self.KSmac
self.current_SE.ssc = stringtoint(rnd_icc[-4:] + rnd_ifd[-4:])
self.current_SE.ct.algorithm = "DES3-CBC"
self.current_SE.cct.algorithm = "CC"
return SW["NORMAL"], Eicc + Micc
def _mac(self, key, data, ssc = None, dopad=True):
if ssc:
data = ssc + data
if dopad:
topad = 8 - len(data) % 8
data = data + "\x80" + ("\x00" * (topad-1))
a = vsCrypto.encrypt("des-cbc", key[:8], data)
b = vsCrypto.decrypt("des-ecb", key[8:16], a[-8:])
c = vsCrypto.encrypt("des-ecb", key[:8], b)
return c
class CryptoflexSAM(SAM):
def __init__(self, mf=None):
SAM.__init__(self, None, None, mf)
self.current_SE = CryptoflexSE(mf)
def generate_public_key_pair(self, p1, p2, data):
asym_key = self.current_SE.generate_public_key_pair(p1, p2, data)
#TODO: Use SE instead (and remove SAM.set_asym_algorithm)
self.set_asym_algorithm(asym_key, 0x07)
return SW["NORMAL"], ""
def perform_security_operation(self, p1, p2, data):
"""
In the cryptoflex card, this is the verify key command. A key is send
to the card in plain text and compared to a key stored in the card.
This is used for authentication
@param data: Contains the key to be verified
@return: SW[NORMAL] in case of success otherwise SW[WARN_NOINFO63]
"""
return SW["NORMAL"], ""
#FIXME
#key = self._get_referenced_key(p1,p2)
#if key == data:
# return SW["NORMAL"], ""
#else:
# return SW["WARN_NOINFO63"], ""
def internal_authenticate(self, p1, p2, data):
data = data[::-1] #Reverse Byte order
sw, data = SAM.internal_authenticate(self, p1, p2, data)
if data != "":
data = data[::-1]
return sw, data
if __name__ == "__main__":
"""
Unit test:

View File

@@ -0,0 +1,121 @@
#
# Copyright (C) 2011 Dominik Oepen
#
# 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.SmartcardSAM import SAM
from virtualsmartcard.SEutils import Security_Environment
from virtualsmartcard.SWutils import SwError, SW
from virtualsmartcard.utils import inttostring
from virtualsmartcard import TLVutils
import struct, logging
class CryptoflexSE(Security_Environment):
def __init__(self, mf):
Security_Environment.__init__(self, mf)
def generate_public_key_pair(self, p1, p2, data):
"""
In the Cryptoflex card this command only supports RSA keys.
@param data: Contains the public exponent used for key generation
@param p1: The keynumber. Can be used later to refer to the generated key
@param p2: Used to specify the keylength.
The mapping is: 0x40 => 256 Bit, 0x60 => 512 Bit, 0x80 => 1024
"""
from Crypto.PublicKey import RSA
from Crypto.Util.randpool import RandomPool
keynumber = p1 #TODO: Check if key exists
keylength_dict = {0x40: 256, 0x60: 512, 0x80: 1024}
if not keylength_dict.has_key(p2):
raise SwError(SW["ERR_INCORRECTP1P2"])
else:
keylength = keylength_dict[p2]
rnd = RandomPool()
PublicKey = RSA.generate(keylength, rnd.get_bytes)
self.dst.key = PublicKey
e_in = struct.unpack("<i", data)
if e_in[0] != 65537:
logging.warning("Warning: Exponents different from 65537 are ignored!" +\
"The Exponent given is %i" % e_in[0])
#Encode Public key
n = PublicKey.__getstate__()['n']
n_str = inttostring(n)
n_str = n_str[::-1]
e = PublicKey.__getstate__()['e']
e_str = inttostring(e, 4)
e_str = e_str[::-1]
pad = 187 * '\x30' #We don't have CRT components, so we need to pad
pk_n = TLVutils.bertlv_pack(((0x81, len(n_str), n_str),
(0x01, len(pad), pad),
(0x82, len(e_str), e_str)))
#Private key
d = PublicKey.__getstate__()['d']
#Write result to FID 10 12 EF-PUB-KEY
df = self.mf.currentDF()
ef_pub_key = df.select("fid", 0x1012)
ef_pub_key.writebinary([0], [pk_n])
data = ef_pub_key.getenc('data')
#Write private key to FID 00 12 EF-PRI-KEY (not necessary?)
#How to encode the private key?
ef_priv_key = df.select("fid", 0x0012)
ef_priv_key.writebinary([0], [inttostring(d)])
data = ef_priv_key.getenc('data')
return PublicKey
class CryptoflexSAM(SAM):
def __init__(self, mf=None):
SAM.__init__(self, None, None, mf)
self.current_SE = CryptoflexSE(mf)
def generate_public_key_pair(self, p1, p2, data):
asym_key = self.current_SE.generate_public_key_pair(p1, p2, data)
#TODO: Use SE instead (and remove SAM.set_asym_algorithm)
self.set_asym_algorithm(asym_key, 0x07)
return SW["NORMAL"], ""
def perform_security_operation(self, p1, p2, data):
"""
In the cryptoflex card, this is the verify key command. A key is send
to the card in plain text and compared to a key stored in the card.
This is used for authentication
@param data: Contains the key to be verified
@return: SW[NORMAL] in case of success otherwise SW[WARN_NOINFO63]
"""
return SW["NORMAL"], ""
#FIXME
#key = self._get_referenced_key(p1,p2)
#if key == data:
# return SW["NORMAL"], ""
#else:
# return SW["WARN_NOINFO63"], ""
def internal_authenticate(self, p1, p2, data):
data = data[::-1] #Reverse Byte order
sw, data = SAM.internal_authenticate(self, p1, p2, data)
if data != "":
data = data[::-1]
return sw, data

View File

@@ -0,0 +1,145 @@
#
# Copyright (C) 2011 Dominik Oepen
#
# 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.SmartcardSAM import SAM
from virtualsmartcard.SEutils import Security_Environment
import virtualsmartcard.CryptoUtils as vsCrypto
from virtualsmartcard.SWutils import SwError, SW
from virtualsmartcard.utils import stringtoint
import hashlib, struct
from os import urandom
class ePass_SE(Security_Environment):
def __init__(self, MF, SE, ssc=None):
self.ssc = ssc
Security_Environment.__init__(self, MF, SE)
def compute_cryptographic_checksum(self, p1, p2, data):
"""
Compute a cryptographic checksum (e.g. MAC) for the given data.
Algorithm and key are specified in the current (CAPDU) SE. The ePass
uses a Send Sequence Counter for MAC calculation
"""
if p1 != 0x8E or p2 != 0x80:
raise SwError(SW["ERR_INCORRECTP1P2"])
self.ssc += 1
checksum = vsCrypto.crypto_checksum(self.cct.algorithm, self.cct.key,
data, self.cct.iv, self.ssc)
return SW["NORMAL"], checksum
class PassportSAM(SAM):
"""
SAM for ICAO ePassport. Implements Basic access control and key derivation
for Secure Messaging.
"""
def __init__(self, mf):
import virtualsmartcard.SmartcardFilesystem as vsFS
ef_dg1 = vsFS.walk(mf, "\x00\x04\x01\x01")
dg1 = ef_dg1.readbinary(5)
self.mrz1 = dg1[:43]
self.mrz2 = dg1[44:]
self.KSeed = None
self.KEnc = None
self.KMac = None
self.KSenc = None
self.KSmac = None
self.__computeKeys()
SAM.__init__(self, None, None, mf)
self.current_SE = ePass_SE(mf, None, None)
self.current_SE.cct.algorithm = "CC"
self.current_SE.ct.algorithm = "DES3-CBC"
def __computeKeys(self):
"""
Computes the keys depending on the machine readable
zone of the passport according to TR-PKI mrtds ICC read-only
access v1.1 annex E.1.
"""
MRZ_information = self.mrz2[0:10] + self.mrz2[13:20] + self.mrz2[21:28]
H = hashlib.sha1(MRZ_information).digest()
self.KSeed = H[:16]
self.KEnc = self.derive_key(self.KSeed, 1)
self.KMac = self.derive_key(self.KSeed, 2)
def derive_key(self, seed, c):
"""
Derive a key according to TR-PKI mrtds ICC read-only access v1.1
annex E.1.
c is either 1 for encryption or 2 for MAC computation.
Returns: Ka + Kb
Note: Does not adjust parity. Nobody uses that anyway ..."""
D = seed + struct.pack(">i", c)
H = hashlib.sha1(D).digest()
Ka = H[0:8]
Kb = H[8:16]
return Ka + Kb
def external_authenticate(self, p1, p2, resp_data):
"""Performs the basic access control protocol as defined in
the ICAO MRTD standard"""
rnd_icc = self.last_challenge
#Receive Mutual Authenticate APDU from terminal
#Decrypt data and check MAC
Eifd = resp_data[:-8]
Mifd = self._mac(self.KMac, Eifd)
#Check the MAC
if not Mifd == resp_data[-8:]:
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
#Decrypt the data
plain = vsCrypto.decrypt("DES3-CBC", self.KEnc, resp_data[:-8])
#Split decrypted data into the two nonces and
if plain[8:16] != rnd_icc:
raise SwError(SW["WARN_NOINFO63"])
#Extract keying material from IFD, generate ICC keying material
Kifd = plain[16:]
rnd_ifd = plain[:8]
Kicc = urandom(16)
#Generate Answer
data = plain[8:16] + plain[:8] + Kicc
Eicc = vsCrypto.encrypt("DES3-CBC", self.KEnc, data)
Micc = self._mac(self.KMac, Eicc)
#Derive the final keys
KSseed = vsCrypto.operation_on_string(Kicc, Kifd, lambda a, b: a^b)
self.KSenc = self.derive_key(KSseed, 1)
self.KSmac = self.derive_key(KSseed, 2)
#self.ssc = rnd_icc[-4:] + rnd_ifd[-4:]
#Set the current SE
self.current_SE.ct.key = self.KSenc
self.current_SE.cct.key = self.KSmac
self.current_SE.ssc = stringtoint(rnd_icc[-4:] + rnd_ifd[-4:])
self.current_SE.ct.algorithm = "DES3-CBC"
self.current_SE.cct.algorithm = "CC"
return SW["NORMAL"], Eicc + Micc
def _mac(self, key, data, ssc = None, dopad=True):
if ssc:
data = ssc + data
if dopad:
topad = 8 - len(data) % 8
data = data + "\x80" + ("\x00" * (topad-1))
a = vsCrypto.encrypt("des-cbc", key[:8], data)
b = vsCrypto.decrypt("des-ecb", key[8:16], a[-8:])
c = vsCrypto.encrypt("des-ecb", key[:8], b)
return c