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:
@@ -3,6 +3,7 @@ CLEANFILES = $(bin_SCRIPTS)
|
|||||||
EXTRA_DIST = vicc.in
|
EXTRA_DIST = vicc.in
|
||||||
|
|
||||||
vpiccdir = $(pythondir)/virtualsmartcard
|
vpiccdir = $(pythondir)/virtualsmartcard
|
||||||
|
vpicccardsdir = $(vpiccdir)/cards
|
||||||
|
|
||||||
vpicc_PYTHON = virtualsmartcard/CardGenerator.py \
|
vpicc_PYTHON = virtualsmartcard/CardGenerator.py \
|
||||||
virtualsmartcard/SEutils.py \
|
virtualsmartcard/SEutils.py \
|
||||||
@@ -16,6 +17,10 @@ vpicc_PYTHON = virtualsmartcard/CardGenerator.py \
|
|||||||
virtualsmartcard/VirtualSmartcard.py \
|
virtualsmartcard/VirtualSmartcard.py \
|
||||||
virtualsmartcard/__init__.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'
|
do_subst = sed -e 's,[@]PYTHON[@],$(PYTHON),g'
|
||||||
|
|
||||||
vicc: vicc.in Makefile
|
vicc: vicc.in Makefile
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from virtualsmartcard.SmartcardFilesystem import MF, DF, TransparentStructureEF
|
|||||||
from virtualsmartcard.ConstantDefinitions import FDB
|
from virtualsmartcard.ConstantDefinitions import FDB
|
||||||
from virtualsmartcard.CryptoUtils import protect_string, read_protected_string
|
from virtualsmartcard.CryptoUtils import protect_string, read_protected_string
|
||||||
import virtualsmartcard.SmartcardSAM
|
import virtualsmartcard.SmartcardSAM
|
||||||
|
from virtualsmartcard.cards import ePass, cryptoflex
|
||||||
|
|
||||||
# pgp directory
|
# pgp directory
|
||||||
#self.mf.append(DF(parent=self.mf,
|
#self.mf.append(DF(parent=self.mf,
|
||||||
@@ -127,7 +128,7 @@ class CardGenerator(object):
|
|||||||
mf.append(df)
|
mf.append(df)
|
||||||
|
|
||||||
self.mf = mf
|
self.mf = mf
|
||||||
self.sam = virtualsmartcard.SmartcardSAM.PassportSAM(self.mf)
|
self.sam = ePass.PassportSAM(self.mf)
|
||||||
|
|
||||||
def __generate_cryptoflex(self):
|
def __generate_cryptoflex(self):
|
||||||
from virtualsmartcard.SmartcardFilesystem import CryptoflexMF
|
from virtualsmartcard.SmartcardFilesystem import CryptoflexMF
|
||||||
|
|||||||
@@ -690,87 +690,4 @@ class Security_Environment(object):
|
|||||||
return SW["NORMAL"], result
|
return SW["NORMAL"], result
|
||||||
else:
|
else:
|
||||||
#FIXME: Where to put the keys?
|
#FIXME: Where to put the keys?
|
||||||
return SW["NORMAL"], ""
|
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
|
|
||||||
@@ -17,14 +17,14 @@
|
|||||||
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
|
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
import struct, hashlib, logging
|
import logging
|
||||||
from pickle import dumps, loads
|
from pickle import dumps, loads
|
||||||
from os import urandom
|
from os import urandom
|
||||||
|
|
||||||
import virtualsmartcard.CryptoUtils as vsCrypto
|
import virtualsmartcard.CryptoUtils as vsCrypto
|
||||||
from virtualsmartcard.SWutils import SwError, SW
|
from virtualsmartcard.SWutils import SwError, SW
|
||||||
from virtualsmartcard.utils import inttostring, stringtoint
|
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):
|
def get_referenced_cipher(p1):
|
||||||
"""
|
"""
|
||||||
@@ -336,139 +336,7 @@ class SAM(object):
|
|||||||
|
|
||||||
def manage_security_environment(self, p1, p2, data):
|
def manage_security_environment(self, p1, p2, data):
|
||||||
return self.current_SE.manage_security_environment(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__":
|
if __name__ == "__main__":
|
||||||
"""
|
"""
|
||||||
Unit test:
|
Unit test:
|
||||||
|
|||||||
121
virtualsmartcard/src/vpicc/virtualsmartcard/cards/cryptoflex.py
Normal file
121
virtualsmartcard/src/vpicc/virtualsmartcard/cards/cryptoflex.py
Normal 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
|
||||||
145
virtualsmartcard/src/vpicc/virtualsmartcard/cards/ePass.py
Normal file
145
virtualsmartcard/src/vpicc/virtualsmartcard/cards/ePass.py
Normal 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
|
||||||
Reference in New Issue
Block a user