Refactoring and Bugfixing:
- CryptoUtils:
- removing hard coded keylength from append_padding in CryptoUtils
- rewrote append_padding, slightly modified strip_padding in CryptoUtils
- added get_cipher_keylen and get_cipher_blocklen to CryptoUtils
- Updated UnitTest in CryptoUtils. Note: PBKDF2.self_test() fails!!!
- added new function protect_string to encrypt and authenticate a string
- Removed hard coded padding pattern
- VirtualICC:
- Added and integrated functions to load and save VirtualICC objects
- Added exception handling for opening of the socket
- SmartcardSAM:
- Removed all object persistance functionaliy from SmartcardSAM
- Changed the constructors of the *SAM classes
- Moved all the functionality to create cards (especially the pre-initialized filesystems for each card type) to the new file GenerateCards.py
- Added minimal command line based interface to GenerateCards.py to generate customized cards and store them in a file
- Removed default sam and mf files (testconfig.mf and testconfig.sam)
git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@35 96b47cad-a561-4643-ad3b-153ac7d7599c
This commit is contained in:
188
virtualsmartcard/vpicc/CardGenerator.py
Normal file
188
virtualsmartcard/vpicc/CardGenerator.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2009 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
import sys, getpass, anydbm
|
||||||
|
from pickle import loads, dumps
|
||||||
|
from TLVutils import pack
|
||||||
|
from utils import inttostring
|
||||||
|
from SmartcardFilesystem import MF, TransparentStructureEF
|
||||||
|
|
||||||
|
# pgp directory
|
||||||
|
#self.mf.append(DF(parent=self.mf,
|
||||||
|
#fid=4, dfname='\xd2\x76\x00\x01\x24\x01', bertlv_data=[]))
|
||||||
|
# pkcs-15 directories
|
||||||
|
#self.mf.append(DF(parent=self.mf,
|
||||||
|
#fid=1, dfname='\xa0\x00\x00\x00\x01'))
|
||||||
|
#self.mf.append(DF(parent=self.mf,
|
||||||
|
#fid=2, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00'))
|
||||||
|
#self.mf.append(DF(parent=self.mf,
|
||||||
|
#fid=3, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00'))
|
||||||
|
|
||||||
|
def generate_iso_card():
|
||||||
|
from SmartcardSAM import SAM
|
||||||
|
|
||||||
|
print "Using default SAM. Insecure!!!"
|
||||||
|
sam = SAM("1234", "1234567890") #FIXME: Use user provided data
|
||||||
|
|
||||||
|
mf = MF(filedescriptor=FDB["DF"], lifecycle=LCB["ACTIVATED"], dfname=None)
|
||||||
|
self.SAM.set_MF(self.mf)
|
||||||
|
|
||||||
|
return mf, sam
|
||||||
|
|
||||||
|
def generate_ePass():
|
||||||
|
from PIL import Image
|
||||||
|
from SmartcardFilesystem import DF
|
||||||
|
from SmartcardSAM import PassportSAM
|
||||||
|
|
||||||
|
MRZ1 = "P<UTOERIKSSON<<ANNA<MARIX<<<<<<<<<<<<<<<<<<<"
|
||||||
|
MRZ2 = "L898902C<3UTO6908061F9406236ZE184226B<<<<<14"
|
||||||
|
MRZ = MRZ1 + MRZ2
|
||||||
|
|
||||||
|
picturepath = "jp2.jpg"
|
||||||
|
try:
|
||||||
|
im = Image.open(picturepath)
|
||||||
|
pic_width, pic_height = im.size
|
||||||
|
fd = open(picturepath,"rb")
|
||||||
|
picture = fd.read()
|
||||||
|
fd.close()
|
||||||
|
except IOError:
|
||||||
|
print "Could not find picture %s" % picturepath
|
||||||
|
pic_width = 0
|
||||||
|
pic_height = 0
|
||||||
|
picture = None
|
||||||
|
|
||||||
|
mf = MF()
|
||||||
|
|
||||||
|
#We need a MF with Application DF \xa0\x00\x00\x02G\x10\x01
|
||||||
|
mf.append(DF(parent=mf, fid=4, dfname='\xa0\x00\x00\x02G\x10\x01', bertlv_data=[]))
|
||||||
|
df = mf.currentDF()
|
||||||
|
mf.append(TransparentStructureEF(parent=df, fid=0x011E, filedescriptor=0, data=""))#EF.COM
|
||||||
|
mf.append(TransparentStructureEF(parent=df, fid=0x0101, filedescriptor=0, data=""))#EF.DG1
|
||||||
|
mf.append(TransparentStructureEF(parent=df, fid=0x0102, filedescriptor=0, data=""))#EF.DG2
|
||||||
|
mf.append(TransparentStructureEF(parent=df, fid=0x010D, filedescriptor=0, data=""))#EF.SOD
|
||||||
|
#EF.COM
|
||||||
|
COM = pack([(0x5F01,4,"0107"),(0x5F36,6,"040000"),(0x5C,2,"6175")])
|
||||||
|
COM = pack(((0x60,len(COM),COM),))
|
||||||
|
fid = df.select("fid",0x011E)
|
||||||
|
fid.writebinary([0],[COM])
|
||||||
|
#EF.DG1
|
||||||
|
DG1 = pack([(0x5F1F,len(MRZ),MRZ)])
|
||||||
|
DG1 = pack([(0x61,len(DG1),DG1)])
|
||||||
|
fid = df.select("fid",0x0101)
|
||||||
|
fid.writebinary([0],[DG1])
|
||||||
|
#EF.DG2
|
||||||
|
if picture != None:
|
||||||
|
IIB = "\x00\x01" + inttostring(pic_width,2) + inttostring(pic_height,2) + 6 * "\x00"
|
||||||
|
length = 32 + len(picture) #32 is the length of IIB + FIB
|
||||||
|
FIB = inttostring(length,4) + 16 * "\x00"
|
||||||
|
FRH = "FAC" + "\x00" + "010" + "\x00" + inttostring(14+length,4) + inttostring(1,2)
|
||||||
|
picture = FRH + FIB + IIB + picture
|
||||||
|
DG2 = pack([(0xA1,8,"\x87\x02\x01\x01\x88\x02\x05\x01"),(0x5F2E,len(picture),picture)])
|
||||||
|
DG2 = pack([(0x02,1,"\x01"),(0x7F60,len(DG2),DG2)])
|
||||||
|
DG2 = pack([(0x7F61,len(DG2),DG2)])
|
||||||
|
fid = df.select("fid",0x0102)
|
||||||
|
fid.writebinary([0],[DG2])
|
||||||
|
|
||||||
|
sam = PassportSAM(mf)
|
||||||
|
return mf, sam
|
||||||
|
|
||||||
|
def generate_cryptoflex():
|
||||||
|
from SmartcardFilesystem import CryptoflexMF
|
||||||
|
from SmartcardSAM import CryptoflexSAM
|
||||||
|
|
||||||
|
mf = CryptoflexMF()
|
||||||
|
mf.append(TransparentStructureEF(parent=mf, fid=0x0002, filedescriptor=0x01,
|
||||||
|
data="\x00\x00\x00\x01\x00\x01\x00\x00")) #EF.ICCSN
|
||||||
|
sam = CryptoflexSAM(mf)
|
||||||
|
|
||||||
|
return mf, sam
|
||||||
|
|
||||||
|
def loadCard(filename, password=None):
|
||||||
|
from CryptoUtils import read_protected_string
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = anydbm.open(filename, 'r')
|
||||||
|
except anydbm.error:
|
||||||
|
print "Failed to open " + filename
|
||||||
|
|
||||||
|
if password is None:
|
||||||
|
password = getpass.getpass("Please enter your password.")
|
||||||
|
|
||||||
|
serializedMF = read_protected_string(db["mf"], password)
|
||||||
|
serializedSAM = read_protected_string(db["sam"], password)
|
||||||
|
SAM = loads(serializedSAM)
|
||||||
|
MF = loads(serializedMF)
|
||||||
|
#self.type = db["type"]
|
||||||
|
|
||||||
|
return SAM, MF
|
||||||
|
|
||||||
|
def saveCard(mf, sam, filename, password=None):
|
||||||
|
from CryptoUtils import protect_string
|
||||||
|
|
||||||
|
if filename != None:
|
||||||
|
print "Saving smartcard configuration to %s" % filename
|
||||||
|
else: #TODO: Ask user for filename
|
||||||
|
pass
|
||||||
|
|
||||||
|
if password is None:
|
||||||
|
passwd1 = getpass.getpass("Please enter a password.")
|
||||||
|
passwd2 = getpass.getpass("Please retype your password.")
|
||||||
|
if (passwd1 != passwd2):
|
||||||
|
raise ValueError, "Passwords did not match. Will now exit"
|
||||||
|
else:
|
||||||
|
password = passwd1
|
||||||
|
|
||||||
|
mf_string = dumps(mf)
|
||||||
|
sam_string = dumps(sam)
|
||||||
|
protectedMF = protect_string(mf_string, password)
|
||||||
|
protectedSAM = protect_string(sam_string, password)
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = anydbm.open(filename, 'c')
|
||||||
|
db["mf"] = protectedMF
|
||||||
|
db["sam"] = protectedSAM
|
||||||
|
#db["type"] = self.type
|
||||||
|
db.close()
|
||||||
|
except anydbm.error:
|
||||||
|
print "Failed to write data to disk"
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
from optparse import OptionParser
|
||||||
|
parser = OptionParser()
|
||||||
|
parser.add_option("-t", "--type", action="store", type="choice",
|
||||||
|
default='iso7816',
|
||||||
|
choices=['iso7816', 'cryptoflex', 'ePass'],
|
||||||
|
help="Type of Smartcard [default: %default]")
|
||||||
|
parser.add_option("-f", "--file", action="store", type="string",
|
||||||
|
dest="filename", default=None,
|
||||||
|
help="Where to save the generated card")
|
||||||
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
|
if options.filename == None:
|
||||||
|
print "You have to provide a filename using the -f option"
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if options.type == 'iso7816':
|
||||||
|
mf, sam = generate_iso_card()
|
||||||
|
elif options.type == 'ePass':
|
||||||
|
mf, sam = generate_ePass()
|
||||||
|
elif options.type == 'cryptoflex':
|
||||||
|
mf, sam = generate_cryptoflex()
|
||||||
|
|
||||||
|
saveCard(mf, sam, options.filename)
|
||||||
@@ -23,6 +23,7 @@ from binascii import b2a_hex
|
|||||||
from random import randint
|
from random import randint
|
||||||
from utils import inttostring, stringtoint, hexdump
|
from utils import inttostring, stringtoint, hexdump
|
||||||
import string
|
import string
|
||||||
|
import re
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Use PyCrypto (if available)
|
# Use PyCrypto (if available)
|
||||||
@@ -33,8 +34,7 @@ except ImportError:
|
|||||||
import hmac as HMAC
|
import hmac as HMAC
|
||||||
import sha as SHA1
|
import sha as SHA1
|
||||||
|
|
||||||
iv = '\x00' * 8
|
CYBERFLEX_IV = '\x00' * 8
|
||||||
PADDING = '\x80' + '\x00' * 7
|
|
||||||
|
|
||||||
## *******************************************************************
|
## *******************************************************************
|
||||||
## * Generic methods *
|
## * Generic methods *
|
||||||
@@ -63,38 +63,64 @@ def get_cipher(cipherspec, key, iv = None):
|
|||||||
|
|
||||||
return cipher
|
return cipher
|
||||||
|
|
||||||
def append_padding(cipherspec, data, padding_class=0x01,keylength = 16):
|
def get_cipher_keylen(cipherspec):
|
||||||
|
cipherparts = cipherspec.split("-")
|
||||||
|
|
||||||
|
if len(cipherparts) > 2:
|
||||||
|
raise ValueError, 'cipherspec must be of the form "cipher-mode" or "cipher"'
|
||||||
|
|
||||||
|
cipher = cipherparts[0].upper()
|
||||||
|
#cipher = globals().get(cipherparts[0].upper(), None)
|
||||||
|
#Note: return cipher.key_size does not work on Ubuntu, because e.g. AES.key_size == 0
|
||||||
|
if cipher == "AES": #Pycrypto uses AES128
|
||||||
|
return 16
|
||||||
|
elif cipher == "DES":
|
||||||
|
return 8
|
||||||
|
elif cipher == "DES3":
|
||||||
|
return 16
|
||||||
|
else:
|
||||||
|
raise ValueError, "Unsupported Encryption Algorithm: " + cipher
|
||||||
|
|
||||||
|
def get_cipher_blocklen(cipherspec):
|
||||||
|
cipherparts = cipherspec.split("-")
|
||||||
|
|
||||||
|
if len(cipherparts) > 2:
|
||||||
|
raise ValueError, 'cipherspec must be of the form "cipher-mode" or "cipher"'
|
||||||
|
|
||||||
|
cipher = globals().get(cipherparts[0].upper(), None)
|
||||||
|
return cipher.block_size
|
||||||
|
|
||||||
|
def append_padding(cipherspec, data, padding_class=0x01):
|
||||||
"""Append padding to the data.
|
"""Append padding to the data.
|
||||||
Length of padding depends on length of data and the block size of the
|
Length of padding depends on length of data and the block size of the
|
||||||
specified encryption algorithm.
|
specified encryption algorithm.
|
||||||
Different types of padding may be selected via the padding_class parameter
|
Different types of padding may be selected via the padding_class parameter
|
||||||
"""
|
"""
|
||||||
key = "DUMMYKEY" * (keylength / 8) #Key doesn't matter for padding
|
blocklen = get_cipher_blocklen(cipherspec)
|
||||||
cipher = get_cipher(cipherspec,key,iv)
|
|
||||||
if padding_class == 0x01: #ISO padding
|
if padding_class == 0x01: #ISO padding
|
||||||
last_block_length = len(data) % cipher.block_size
|
last_block_length = len(data) % blocklen
|
||||||
padding_length = cipher.block_size - last_block_length
|
padding_length = blocklen - last_block_length
|
||||||
if padding_length == 0:
|
if padding_length == 0:
|
||||||
padding = PADDING
|
padding = '\x80' + '\x00' * (blocklen - 1)
|
||||||
else:
|
else:
|
||||||
padding = PADDING[:padding_length]
|
padding = '\x80' + '\x00' * (blocklen - last_block_length - 1)
|
||||||
|
|
||||||
del cipher
|
|
||||||
return data + padding
|
return data + padding
|
||||||
|
|
||||||
def strip_padding(cipherspec,data,padding_class=0x01,keylength = 16):
|
def strip_padding(cipherspec,data,padding_class=0x01):
|
||||||
"""
|
"""
|
||||||
Strip the padding of decrypted data. Returns data without padding
|
Strip the padding of decrypted data. Returns data without padding
|
||||||
"""
|
"""
|
||||||
key = "DUMMYKEY" * (keylength / 8) #Key doesn't matter for padding
|
|
||||||
cipher = get_cipher(cipherspec,key,iv)
|
#TODO: Sanity check
|
||||||
if padding_class == 0x01:
|
if padding_class == 0x01:
|
||||||
tail = len(data) - 1
|
tail = len(data) - 1
|
||||||
while data[tail] != '\x80':
|
while data[tail] != '\x80':
|
||||||
tail = tail - 1
|
tail = tail - 1
|
||||||
return data[:tail]
|
return data[:tail]
|
||||||
|
|
||||||
def crypto_checksum(algo,key,data,iv=None,ssc=None):
|
def crypto_checksum(algo, key, data, iv=None, ssc=None):
|
||||||
if algo not in ("HMAC","MAC","CC"):
|
if algo not in ("HMAC","MAC","CC"):
|
||||||
raise ValueError, "Unknown Algorithm %s" % algo
|
raise ValueError, "Unknown Algorithm %s" % algo
|
||||||
|
|
||||||
@@ -119,7 +145,7 @@ def cipher(do_encrypt, cipherspec, key, data, iv = None):
|
|||||||
operation = do_encrypt ? encrypt : decrypt,
|
operation = do_encrypt ? encrypt : decrypt,
|
||||||
cipherspec must be of the form "cipher-mode", or "cipher\""""
|
cipherspec must be of the form "cipher-mode", or "cipher\""""
|
||||||
|
|
||||||
cipher = get_cipher(cipherspec,key,iv)
|
cipher = get_cipher(cipherspec, key, iv)
|
||||||
|
|
||||||
result = None
|
result = None
|
||||||
if do_encrypt:
|
if do_encrypt:
|
||||||
@@ -148,37 +174,92 @@ def operation_on_string(string1, string2, op):
|
|||||||
result.append( chr(op(ord(string1[i]),ord(string2[i]))) )
|
result.append( chr(op(ord(string1[i]),ord(string2[i]))) )
|
||||||
return "".join(result)
|
return "".join(result)
|
||||||
|
|
||||||
|
def protect_string(string, password, cipherspec=None):
|
||||||
|
"""
|
||||||
|
Encrypt and authenticate a string
|
||||||
|
"""
|
||||||
|
|
||||||
|
#Derive a key and a salt from password
|
||||||
|
pbk = crypt(password)
|
||||||
|
regex = re.compile('\$p5k2\$\$[\w./]*\$')
|
||||||
|
match = regex.match(pbk)
|
||||||
|
if match != None:
|
||||||
|
salt = pbk[7:match.end()-1]
|
||||||
|
key = pbk[match.end():]
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
#Encrypt the string, authenticate and format it
|
||||||
|
if cipherspec == None:
|
||||||
|
cipherspec = "AES-CBC"
|
||||||
|
else:
|
||||||
|
pass #TODO: Sanity check for cipher
|
||||||
|
paddedString = append_padding(cipherspec, string)
|
||||||
|
cryptedString = cipher(True, cipherspec, key, paddedString)
|
||||||
|
hmac = crypto_checksum("HMAC", key, cryptedString)
|
||||||
|
protectedString = "$p5k2$$" + salt + "$" + cryptedString + hmac
|
||||||
|
|
||||||
|
return protectedString
|
||||||
|
|
||||||
|
def read_protected_string(string, password, cipherspec=None):
|
||||||
|
"""
|
||||||
|
Decrypt a protected string and verify the authentication data
|
||||||
|
"""
|
||||||
|
hmac_length = 32 #FIXME: Ugly
|
||||||
|
|
||||||
|
#Check if the string has the structure, that is generated by protect_string
|
||||||
|
|
||||||
|
regex = re.compile('\$p5k2\$\$[\w./]*\$') #TODO: Ensure the right format!
|
||||||
|
match = regex.match(string)
|
||||||
|
if match != None:
|
||||||
|
crypted = string[match.end():len(string) - hmac_length]
|
||||||
|
salt = string[7:match.end() - 1]
|
||||||
|
hmac = string[len(string) - hmac_length:]
|
||||||
|
else:
|
||||||
|
raise ValueError, "Wrong string format"
|
||||||
|
|
||||||
|
#Derive key
|
||||||
|
pbk = crypt(password, salt)
|
||||||
|
match = regex.match(pbk)
|
||||||
|
key = pbk[match.end():]
|
||||||
|
|
||||||
|
#Verify HMAC
|
||||||
|
checksum = crypto_checksum("HMAC", key, crypted)
|
||||||
|
if checksum != hmac:
|
||||||
|
print "Found HMAC %s expected %s" % (str(hmac), str(checksum))
|
||||||
|
raise ValueError, "Failed to authenticate data. Wrong password?"
|
||||||
|
|
||||||
|
#Decrypt data
|
||||||
|
if cipherspec == None:
|
||||||
|
cipherspec = "AES-CBC"
|
||||||
|
decrypted = cipher(False, cipherspec, key, crypted)
|
||||||
|
|
||||||
|
return strip_padding(cipherspec, decrypted)
|
||||||
|
|
||||||
## *******************************************************************
|
## *******************************************************************
|
||||||
## * Cyberflex specific methods *
|
## * Cyberflex specific methods *
|
||||||
## *******************************************************************
|
## *******************************************************************
|
||||||
def verify_card_cryptogram(session_key, host_challenge,
|
def verify_card_cryptogram(session_key, host_challenge, card_challenge, card_cryptogram):
|
||||||
card_challenge, card_cryptogram):
|
|
||||||
message = host_challenge + card_challenge
|
message = host_challenge + card_challenge
|
||||||
expected = calculate_MAC(session_key, message, iv)
|
expected = calculate_MAC(session_key, message, CYBERFLEX_IV)
|
||||||
|
|
||||||
print >>sys.stderr, "Original: %s" % binascii.b2a_hex(card_cryptogram)
|
|
||||||
print >>sys.stderr, "Expected: %s" % binascii.b2a_hex(expected)
|
|
||||||
|
|
||||||
return card_cryptogram == expected
|
return card_cryptogram == expected
|
||||||
|
|
||||||
def calculate_host_cryptogram(session_key, card_challenge,
|
def calculate_host_cryptogram(session_key, card_challenge, host_challenge):
|
||||||
host_challenge):
|
|
||||||
message = card_challenge + host_challenge
|
message = card_challenge + host_challenge
|
||||||
return calculate_MAC(session_key, message, iv)
|
return calculate_MAC(session_key, message, CYBERFLEX_IV)
|
||||||
|
|
||||||
def calculate_MAC(session_key, message, iv):
|
def calculate_MAC(session_key, message, iv=CYBERFLEX_IV):
|
||||||
print >>sys.stderr, "Doing MAC for: %s" % utils.hexdump(message, indent = 17)
|
""""
|
||||||
|
Cyberflex MAC is the last Block of the input encrypted with DES3-CBC
|
||||||
|
"""
|
||||||
|
|
||||||
cipher = DES3.new(session_key, DES3.MODE_CBC, iv)
|
cipher = DES3.new(session_key, DES3.MODE_CBC, iv)
|
||||||
block_count = len(message) / cipher.block_size
|
padded = append_padding("DES3", message, 0x01)
|
||||||
for i in range(block_count):
|
block_count = len(padded) / cipher.block_size
|
||||||
cipher.encrypt(message[i*cipher.block_size:(i+1)*cipher.block_size])
|
crypted = cipher.encrypt(padded)
|
||||||
|
|
||||||
last_block_length = len(message) % cipher.block_size
|
return crypted[len(padded) - cipher.block_size : ]
|
||||||
last_block = (message[len(message)-last_block_length:]+PADDING)[:cipher.block_size]
|
|
||||||
|
|
||||||
return cipher.encrypt( last_block )
|
|
||||||
|
|
||||||
def get_derivation_data(host_challenge, card_challenge):
|
def get_derivation_data(host_challenge, card_challenge):
|
||||||
return card_challenge[4:8] + host_challenge[:4] + \
|
return card_challenge[4:8] + host_challenge[:4] + \
|
||||||
@@ -546,11 +627,22 @@ if __name__ == "__main__":
|
|||||||
print "Host-Crypto: ", utils.hexdump( host_crypto )
|
print "Host-Crypto: ", utils.hexdump( host_crypto )
|
||||||
|
|
||||||
external_authenticate = binascii.a2b_hex("".join("84 82 01 00 10".split())) + host_crypto
|
external_authenticate = binascii.a2b_hex("".join("84 82 01 00 10".split())) + host_crypto
|
||||||
print utils.hexdump(calculate_MAC(session_key, external_authenticate, iv))
|
print utils.hexdump(calculate_MAC(session_key, external_authenticate))
|
||||||
|
|
||||||
too_short = binascii.a2b_hex("".join("89 45 19 BF".split()))
|
too_short = binascii.a2b_hex("".join("89 45 19 BF".split()))
|
||||||
padded = append_padding("DES3-ECB",len(too_short),too_short)
|
padded = append_padding("DES3-ECB", too_short)
|
||||||
print "Padded data: " + utils.hexdump(padded)
|
print "Padded data: " + utils.hexdump(padded)
|
||||||
unpadded = strip_padding("DES3-ECB",padded)
|
unpadded = strip_padding("DES3-ECB", padded)
|
||||||
print "Without padding: " + utils.hexdump(unpadded)
|
print "Without padding: " + utils.hexdump(unpadded)
|
||||||
test_pbkdf2()
|
|
||||||
|
teststring = "DEADBEEFistatsyksdvhihewohfwoehcowc8hw8rogfq8whv75tsgohsav8wress"
|
||||||
|
foo = append_padding("AES", teststring)
|
||||||
|
print len(foo)
|
||||||
|
print strip_padding("AES", foo)
|
||||||
|
testpass = "SomeRandomPassphrase"
|
||||||
|
protectedString = protect_string(teststring, testpass)
|
||||||
|
unprotectedString = read_protected_string(protectedString, testpass)
|
||||||
|
if teststring != unprotectedString:
|
||||||
|
print "protect_string test failed"
|
||||||
|
|
||||||
|
#test_pbkdf2()
|
||||||
|
|||||||
@@ -26,16 +26,6 @@ from Crypto.Cipher import DES3
|
|||||||
from ConstantDefinitions import *
|
from ConstantDefinitions import *
|
||||||
from SEutils import ControlReferenceTemplate as CRT
|
from SEutils import ControlReferenceTemplate as CRT
|
||||||
|
|
||||||
def generate_SAM_config(cardNumber,PIN,cardSecret,path,password):
|
|
||||||
"""
|
|
||||||
This method generates a new SAM configuration using the given parameters,
|
|
||||||
stores it in a KeyContainer, serialises the container, encrypts it and
|
|
||||||
writes it to the disk.
|
|
||||||
"""
|
|
||||||
container = CardContainer(cardNumber,PIN,cardSecret)
|
|
||||||
container.saveConfiguration(path,password)
|
|
||||||
del container
|
|
||||||
|
|
||||||
def get_referenced_cipher(p1):
|
def get_referenced_cipher(p1):
|
||||||
"""
|
"""
|
||||||
P1 defines the algorithm and mode to use. We dispatch it and return a
|
P1 defines the algorithm and mode to use. We dispatch it and return a
|
||||||
@@ -43,6 +33,7 @@ def get_referenced_cipher(p1):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
ciphertable = {
|
ciphertable = {
|
||||||
|
0x00: "DES3-ECB",
|
||||||
0x01: "DES3-ECB",
|
0x01: "DES3-ECB",
|
||||||
0x02: "DES3-CBC",
|
0x02: "DES3-CBC",
|
||||||
0x03: "DES-ECB",
|
0x03: "DES-ECB",
|
||||||
@@ -67,17 +58,26 @@ class CardContainer:
|
|||||||
to the corresponding container.
|
to the corresponding container.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,cardNumber=None,PIN=None,cardSecret=None):
|
def __init__(self, PIN=None, cardNumber=None, cardSecret=None):
|
||||||
|
from os import urandom
|
||||||
|
|
||||||
self.cardNumber = cardNumber
|
self.cardNumber = cardNumber
|
||||||
self.PIN = PIN
|
self.PIN = PIN
|
||||||
self.cardSecret = cardSecret
|
|
||||||
self.FSkeys = {}
|
self.FSkeys = {}
|
||||||
self.cipher = 0x01 # Algorithm reference defined in __get_referenced_algorithm(p1)
|
self.cipher = 0x01 # Algorithm reference defined in __get_referenced_algorithm(p1)
|
||||||
self.block_size = 16 #Need to be set according to the needs of self.cipher. Ugly!
|
|
||||||
self.master_password = None
|
self.master_password = None
|
||||||
self.master_key = None
|
self.master_key = None
|
||||||
self.salt = None
|
self.salt = None
|
||||||
self.asym_key = None
|
self.asym_key = None
|
||||||
|
|
||||||
|
keylen = CryptoUtils.get_cipher_keylen(get_referenced_cipher(self.cipher))
|
||||||
|
if cardSecret is None: #Generate a random card secret
|
||||||
|
self.cardSecret = urandom(keylen)
|
||||||
|
else:
|
||||||
|
if len(cardSecret) != keylen:
|
||||||
|
raise ValueError, "cardSecret has the wrong key length for: " + get_referenced_cipher(self.cipher)
|
||||||
|
else:
|
||||||
|
self.cardSecret = cardSecret
|
||||||
|
|
||||||
def __delete__(self):
|
def __delete__(self):
|
||||||
print "Smartcard configuration is NOT saved!!!"
|
print "Smartcard configuration is NOT saved!!!"
|
||||||
@@ -119,135 +119,21 @@ class CardContainer:
|
|||||||
return plain_key
|
return plain_key
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def loadConfiguration(self,path,password):
|
|
||||||
"""
|
|
||||||
Reads the configuration of a key container from the disk, decrypts
|
|
||||||
it and applies it to the current key_ container.
|
|
||||||
The current configuration gets overwritten. The HMAC is used to verify
|
|
||||||
data integrity.
|
|
||||||
"""
|
|
||||||
decryptedContainer = self.loadFromDisk(path, password, True)
|
|
||||||
container = loads(decryptedContainer)
|
|
||||||
self.PIN = container.PIN
|
|
||||||
self.cardNumber = container.cardNumber
|
|
||||||
self.cardSecret = container.cardSecret
|
|
||||||
self.FSkeys = container.FSkeys
|
|
||||||
self.master_password = password
|
|
||||||
del decryptedContainer
|
|
||||||
del container
|
|
||||||
|
|
||||||
def saveConfiguration(self,path,password=None):
|
|
||||||
"""
|
|
||||||
Encrypts the configuration of the current key container and writes it
|
|
||||||
to the disk. Password is used to derivate a key using PBKDF2. A salt and
|
|
||||||
a HMAC is stored along with the data
|
|
||||||
"""
|
|
||||||
serialisedContainer = dumps(self)
|
|
||||||
if self.master_key == None: #New Configuration
|
|
||||||
if password == None:
|
|
||||||
raise ValueError, "Need password but none specified"
|
|
||||||
pbk = CryptoUtils.crypt(password)
|
|
||||||
regex = re.compile('\$p5k2\$\$[\w./]*\$')
|
|
||||||
match = regex.match(pbk)
|
|
||||||
if match != None:
|
|
||||||
self.salt = pbk[7:match.end()-1]
|
|
||||||
self.master_key = pbk[match.end():]
|
|
||||||
else:
|
|
||||||
raise ValueError
|
|
||||||
del pbk
|
|
||||||
|
|
||||||
self.saveToDisk(serialisedContainer,path)
|
|
||||||
|
|
||||||
def saveToDisk(self,data,path,password=None):
|
|
||||||
if password == None:
|
|
||||||
key = self.master_key
|
|
||||||
salt = self.salt
|
|
||||||
else:
|
|
||||||
regex = re.compile('\$p5k2\$\$[\w./]*\$')
|
|
||||||
pbk = CryptoUtils.crypt(password)
|
|
||||||
match = regex.match(pbk)
|
|
||||||
salt = pbk[7:match.end()-1]
|
|
||||||
print salt
|
|
||||||
key = pbk[match.end():]
|
|
||||||
|
|
||||||
cipher = get_referenced_cipher(self.cipher)
|
|
||||||
paddedData = CryptoUtils.append_padding(cipher,data)
|
|
||||||
cryptedData = CryptoUtils.cipher(True,cipher,key,paddedData)
|
|
||||||
hmac = CryptoUtils.crypto_checksum("HMAC",key,cryptedData)
|
|
||||||
data = "$p5k2$$" + salt + "$" + cryptedData + hmac
|
|
||||||
|
|
||||||
try:
|
|
||||||
fd = open(path,"w")
|
|
||||||
fd.write(data)
|
|
||||||
fd.close()
|
|
||||||
except IOError:
|
|
||||||
print "Failed to write data to disk"
|
|
||||||
|
|
||||||
|
|
||||||
def loadFromDisk(self,path,password=None,loading_sam=False):
|
|
||||||
hmac_length = 32 #FIXME: Ugly
|
|
||||||
#Read data
|
|
||||||
try:
|
|
||||||
fd = open(path,"r")
|
|
||||||
data = fd.read()
|
|
||||||
fd.close()
|
|
||||||
except IOError:
|
|
||||||
print "Failed to read data from disk"
|
|
||||||
#Check file structure, ectract components
|
|
||||||
regex = re.compile('\$p5k2\$\$[\w./]*\$') #TODO: Ensure the right format!
|
|
||||||
match = regex.match(data)
|
|
||||||
if match != None:
|
|
||||||
crypted = data[match.end():len(data) - hmac_length]
|
|
||||||
salt = data[7:match.end() - 1]
|
|
||||||
hmac = data[len(data) - hmac_length:]
|
|
||||||
else:
|
|
||||||
raise ValueError, "Wrong file format"
|
|
||||||
if password == None:
|
|
||||||
if salt != self.salt:
|
|
||||||
raise ValueError, "Could not decode data"
|
|
||||||
else:
|
|
||||||
key = self.master_key
|
|
||||||
else:
|
|
||||||
pbk = CryptoUtils.crypt(password,salt)
|
|
||||||
match = regex.match(pbk)
|
|
||||||
key = pbk[match.end():]
|
|
||||||
#Verify HMAC
|
|
||||||
checksum = CryptoUtils.crypto_checksum("HMAC",key,crypted)
|
|
||||||
if checksum != hmac:
|
|
||||||
print "Found HMAC %s expected %s" % (str(hmac), str(checksum))
|
|
||||||
raise ValueError, "Failed to authenticate data. Wrong password?"
|
|
||||||
#Decrypt data
|
|
||||||
cipher = get_referenced_cipher(self.cipher)
|
|
||||||
decrypted = CryptoUtils.cipher(False,cipher,key,crypted)
|
|
||||||
|
|
||||||
#If we are loading the SAM config (paramter loading_sam) we set
|
|
||||||
#the master key and the salt
|
|
||||||
#This is ugly and should be changed
|
|
||||||
if loading_sam:
|
|
||||||
self.master_key = key
|
|
||||||
self.salt = salt
|
|
||||||
|
|
||||||
return decrypted
|
|
||||||
|
|
||||||
|
|
||||||
class SAM(object):
|
class SAM(object):
|
||||||
|
|
||||||
def __init__(self,path,password,mf=None):
|
def __init__(self, PIN, cardNumber, mf=None):
|
||||||
"""
|
|
||||||
Reads the encrypted SAM configuration from the disk and applies it.
|
self.CardContainer = CardContainer(PIN, cardNumber)
|
||||||
"""
|
#self.CardContainer.loadConfiguration(path, password)
|
||||||
self.CardContainer = CardContainer()
|
|
||||||
if len(password) % self.CardContainer.block_size != 0:
|
|
||||||
print "Wrong key length, must be a multiple of %s Bytes" % self.CardContainer.block_size
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
self.CardContainer.loadConfiguration(path, password)
|
|
||||||
self.mf = mf
|
self.mf = mf
|
||||||
self.card_number = self.CardContainer.cardNumber
|
|
||||||
|
self.cardNumber = cardNumber
|
||||||
|
self.cardSecret = self.CardContainer.cardSecret
|
||||||
|
|
||||||
self.last_challenge = None #Will contain non-readable binary string
|
self.last_challenge = None #Will contain non-readable binary string
|
||||||
self.counter = 3 #Number of tries for PIN validation
|
self.counter = 3 #Number of tries for PIN validation
|
||||||
self.cardSecret = self.CardContainer.cardSecret
|
|
||||||
self.SM_handler = Secure_Messaging(self.mf)
|
self.SM_handler = Secure_Messaging(self.mf)
|
||||||
|
|
||||||
def set_MF(self,mf):
|
def set_MF(self,mf):
|
||||||
@@ -322,6 +208,7 @@ class SAM(object):
|
|||||||
return SW["NORMAL"], ""
|
return SW["NORMAL"], ""
|
||||||
else:
|
else:
|
||||||
self.counter -= 1
|
self.counter -= 1
|
||||||
|
print self.CardContainer.getPIN() + " != " + PIN
|
||||||
raise SwError(SW["WARN_NOINFO63"])
|
raise SwError(SW["WARN_NOINFO63"])
|
||||||
#raise SwError(0X63C0 + self.counter) #Be verbose
|
#raise SwError(0X63C0 + self.counter) #Be verbose
|
||||||
else:
|
else:
|
||||||
@@ -343,16 +230,16 @@ class SAM(object):
|
|||||||
to prove key posession
|
to prove key posession
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if p1 == 0x00: #No information given
|
if p1 == 0x00: #No information given
|
||||||
cipher = get_referenced_cipher(self.CardContainer.cipher)
|
cipher = get_referenced_cipher(self.CardContainer.cipher)
|
||||||
else:
|
else:
|
||||||
cipher = get_referenced_cipher(p1)
|
cipher = get_referenced_cipher(p1)
|
||||||
|
|
||||||
if cipher == "RSA" or cipher == "DSA":
|
if cipher == "RSA" or cipher == "DSA":
|
||||||
crypted_challenge = self.CardContainer.asym_key.sign(data,"")
|
crypted_challenge = self.CardContainer.asym_key.sign(data,"")
|
||||||
crypted_challenge = crypted_challenge[0]
|
crypted_challenge = crypted_challenge[0]
|
||||||
crypted_challenge = inttostring(crypted_challenge)
|
crypted_challenge = inttostring(crypted_challenge)
|
||||||
else:
|
else:
|
||||||
key = self._get_referenced_key(p1,p2)
|
key = self._get_referenced_key(p1,p2)
|
||||||
crypted_challenge = CryptoUtils.cipher(True,cipher,key,data)
|
crypted_challenge = CryptoUtils.cipher(True,cipher,key,data)
|
||||||
|
|
||||||
@@ -442,7 +329,7 @@ class SAM(object):
|
|||||||
return SW["NORMAL"], self.last_challenge
|
return SW["NORMAL"], self.last_challenge
|
||||||
|
|
||||||
def get_card_number(self):
|
def get_card_number(self):
|
||||||
return SW["NORMAL"], inttostring(self.card_number)
|
return SW["NORMAL"], inttostring(self.cardNumber)
|
||||||
|
|
||||||
def _get_referenced_key(self,p1,p2):
|
def _get_referenced_key(self,p1,p2):
|
||||||
"""
|
"""
|
||||||
@@ -464,8 +351,7 @@ class SAM(object):
|
|||||||
key = None
|
key = None
|
||||||
qualifier = p2 & 0x1F
|
qualifier = p2 & 0x1F
|
||||||
algo = get_referenced_cipher(p1)
|
algo = get_referenced_cipher(p1)
|
||||||
cipher = CryptoUtils.get_cipher(p1)
|
keylength = CryptoUtils.get_cipher_keylen(algo)
|
||||||
keylength = cipher.key_size
|
|
||||||
|
|
||||||
if (p2 == 0x00): #No information given, use the global card key
|
if (p2 == 0x00): #No information given, use the global card key
|
||||||
key = self.CardContainer.cardSecret
|
key = self.CardContainer.cardSecret
|
||||||
@@ -507,19 +393,6 @@ class SAM(object):
|
|||||||
sw, result = file.readbinary(0)
|
sw, result = file.readbinary(0)
|
||||||
return sw, result
|
return sw, result
|
||||||
|
|
||||||
#The following commands define the interface to the CardContainer functions
|
|
||||||
def saveConfiguration(self,path,password=None):
|
|
||||||
return self.CardContainer.saveConfiguration(path, password)
|
|
||||||
|
|
||||||
def loadConfiguration(self,path,password):
|
|
||||||
return self.CardContainer.loadConfiguration(path, password)
|
|
||||||
|
|
||||||
def loadFromDisk(self,path,password=None):
|
|
||||||
return self.CardContainer.loadFromDisk(path, password)
|
|
||||||
|
|
||||||
def saveToDisk(self,data,path,password=None):
|
|
||||||
return self.CardContainer.saveToDisk(data, path, password)
|
|
||||||
|
|
||||||
#The following commands define the interface to the Secure Messaging functions
|
#The following commands define the interface to the Secure Messaging functions
|
||||||
def generate_public_key_pair(self,p1,p2,data):
|
def generate_public_key_pair(self,p1,p2,data):
|
||||||
return self.SM_handler.generate_public_key_pair(p1, p2, data)
|
return self.SM_handler.generate_public_key_pair(p1, p2, data)
|
||||||
@@ -537,7 +410,7 @@ class SAM(object):
|
|||||||
return self.SM_handler.manage_security_environment(p1, p2, data)
|
return self.SM_handler.manage_security_environment(p1, p2, data)
|
||||||
|
|
||||||
class PassportSAM(SAM):
|
class PassportSAM(SAM):
|
||||||
def __init__(self,mf,path="testconfig.sam",key="DUMMYKEYDUMMYKEY"):
|
def __init__(self, mf):
|
||||||
df = mf.currentDF()
|
df = mf.currentDF()
|
||||||
ef_dg1 = df.select("fid", 0x0101)
|
ef_dg1 = df.select("fid", 0x0101)
|
||||||
dg1 = ef_dg1.readbinary(5)
|
dg1 = ef_dg1.readbinary(5)
|
||||||
@@ -549,8 +422,9 @@ class PassportSAM(SAM):
|
|||||||
self.KSenc = None
|
self.KSenc = None
|
||||||
self.KSmac = None
|
self.KSmac = None
|
||||||
self.__computeKeys()
|
self.__computeKeys()
|
||||||
SAM.__init__(self,path,key,None)
|
#SAM.__init__(self, path, key,None)
|
||||||
self.SM_handler = ePass_SM(mf,None,None,None)
|
SAM.__init__(self, None, None, mf)
|
||||||
|
self.SM_handler = ePass_SM(mf, None, None, None)
|
||||||
self.SM_handler.CAPDU_SE.cct.algo = "CC"
|
self.SM_handler.CAPDU_SE.cct.algo = "CC"
|
||||||
self.SM_handler.RAPDU_SE.cct.algo = "CC"
|
self.SM_handler.RAPDU_SE.cct.algo = "CC"
|
||||||
self.SM_handler.CAPDU_SE.ct.algo = "DES3-CBC"
|
self.SM_handler.CAPDU_SE.ct.algo = "DES3-CBC"
|
||||||
@@ -634,8 +508,8 @@ class PassportSAM(SAM):
|
|||||||
return c
|
return c
|
||||||
|
|
||||||
class CryptoflexSAM(SAM):
|
class CryptoflexSAM(SAM):
|
||||||
def __init__(self,path="testconfig.sam",key="DUMMYKEYDUMMYKEY",mf=None):
|
def __init__(self, mf=None):
|
||||||
SAM.__init__(self,path,key,mf)
|
SAM.__init__(self, None, None, mf)
|
||||||
self.SM_handler = CryptoflexSM(mf)
|
self.SM_handler = CryptoflexSM(mf)
|
||||||
|
|
||||||
def generate_public_key_pair(self,p1,p2,data):
|
def generate_public_key_pair(self,p1,p2,data):
|
||||||
@@ -847,7 +721,7 @@ class Secure_Messaging(object):
|
|||||||
|
|
||||||
if header_authentication:
|
if header_authentication:
|
||||||
to_authenticate = inttostring(CAPDU.cla) + inttostring(CAPDU.ins) + inttostring(CAPDU.p1) + inttostring(CAPDU.p2)
|
to_authenticate = inttostring(CAPDU.cla) + inttostring(CAPDU.ins) + inttostring(CAPDU.p1) + inttostring(CAPDU.p2)
|
||||||
to_authenticate = CryptoUtils.append_padding("DES-CBC",to_authenticate,0x01,8)
|
to_authenticate = CryptoUtils.append_padding("DES-CBC", to_authenticate)
|
||||||
else:
|
else:
|
||||||
to_authenticate = ""
|
to_authenticate = ""
|
||||||
|
|
||||||
@@ -915,7 +789,7 @@ class Secure_Messaging(object):
|
|||||||
|
|
||||||
#SM data objects for authentication
|
#SM data objects for authentication
|
||||||
if tag == SM_Class["CHECKSUM"]:
|
if tag == SM_Class["CHECKSUM"]:
|
||||||
auth = CryptoUtils.append_padding("DES-CBC",to_authenticate,0x01,8)
|
auth = CryptoUtils.append_padding("DES-CBC", to_authenticate)
|
||||||
sw, checksum = self.compute_cryptographic_checksum(0x8E, 0x80, auth)
|
sw, checksum = self.compute_cryptographic_checksum(0x8E, 0x80, auth)
|
||||||
if checksum != value:
|
if checksum != value:
|
||||||
print "Failed to verify checksum!"
|
print "Failed to verify checksum!"
|
||||||
@@ -1002,7 +876,7 @@ class Secure_Messaging(object):
|
|||||||
raise SwError(SW["CONDITIONSNOTSATISFIED"])
|
raise SwError(SW["CONDITIONSNOTSATISFIED"])
|
||||||
elif self.CAPDU_SE.cct.algo == "CCT":
|
elif self.CAPDU_SE.cct.algo == "CCT":
|
||||||
tag = SM_Class["CHECKSUM"]
|
tag = SM_Class["CHECKSUM"]
|
||||||
to_auth = CryptoUtils.append_padding("DES-ECB",return_data,0x01,8)
|
to_auth = CryptoUtils.append_padding("DES-ECB", return_data)
|
||||||
sw, auth = self.compute_cryptographic_checksum(0x8E, 0x80, to_auth)
|
sw, auth = self.compute_cryptographic_checksum(0x8E, 0x80, to_auth)
|
||||||
length = len(auth)
|
length = len(auth)
|
||||||
return_data += TLVutils.pack([(tag,length,auth)])
|
return_data += TLVutils.pack([(tag,length,auth)])
|
||||||
@@ -1192,8 +1066,8 @@ class Secure_Messaging(object):
|
|||||||
if key == None or algo == None:
|
if key == None or algo == None:
|
||||||
return SW["ERR_CONDITIONNOTSATISFIED"], ""
|
return SW["ERR_CONDITIONNOTSATISFIED"], ""
|
||||||
else:
|
else:
|
||||||
padded = CryptoUtils.append_padding(algo,data)
|
padded = CryptoUtils.append_padding(algo, data)
|
||||||
crypted = CryptoUtils.cipher(True,algo,key,padded,self.current_SE.ct.iv)
|
crypted = CryptoUtils.cipher(True, algo, key, padded, self.current_SE.ct.iv)
|
||||||
return SW["NORMAL"], crypted
|
return SW["NORMAL"], crypted
|
||||||
|
|
||||||
def decipher(self,p1,p2,data):
|
def decipher(self,p1,p2,data):
|
||||||
@@ -1344,27 +1218,26 @@ if __name__ == "__main__":
|
|||||||
Unit test:
|
Unit test:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path = "testconfig.sam"
|
|
||||||
password = "DUMMYKEYDUMMYKEY"
|
password = "DUMMYKEYDUMMYKEY"
|
||||||
generate_SAM_config("1234567890","1234","keykeykeykeykeyk",path,password)
|
#generate_SAM_config("1234567890", "1234", "keykeykeykeykeyk", path, password)
|
||||||
|
|
||||||
MyCard = SAM(path,password,None)
|
MyCard = SAM("1234", "1234567890")
|
||||||
try:
|
try:
|
||||||
print MyCard.verify(0x00, 0x00, "5678")
|
print MyCard.verify(0x00, 0x00, "5678")
|
||||||
except SwError, e:
|
except SwError, e:
|
||||||
print e.message
|
print e.message
|
||||||
|
|
||||||
print "Counter = " + str(MyCard.counter)
|
print "Counter = " + str(MyCard.counter)
|
||||||
print MyCard.verify(0x00, 0x00, "1234")
|
print MyCard.verify(0x00, 0x00, "1234")
|
||||||
print "Counter = " + str(MyCard.counter)
|
print "Counter = " + str(MyCard.counter)
|
||||||
sw, challenge = MyCard.get_challenge(0x00,0x00,"")
|
sw, challenge = MyCard.get_challenge(0x00, 0x00, "")
|
||||||
print "Before encryption: " + challenge
|
print "Before encryption: " + challenge
|
||||||
padded = CryptoUtils.append_padding("DES3-ECB",challenge)
|
padded = CryptoUtils.append_padding("DES3-ECB", challenge)
|
||||||
sw, result = MyCard.internal_authenticate(0x00,0x00,padded)
|
sw, result = MyCard.internal_authenticate(0x00, 0x00, padded)
|
||||||
print "Internal Authenticate status code: %x" % sw
|
print "Internal Authenticate status code: %x" % sw
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sw, res = MyCard.external_authenticate(0x00,0x00,result)
|
sw, res = MyCard.external_authenticate(0x00, 0x00, result)
|
||||||
except SwError, e:
|
except SwError, e:
|
||||||
print e.message
|
print e.message
|
||||||
sw = e.sw
|
sw = e.sw
|
||||||
|
|||||||
@@ -25,42 +25,17 @@ from SmartcardSAM import CardContainer, SAM, PassportSAM, CryptoflexSAM
|
|||||||
from pickle import dumps, loads
|
from pickle import dumps, loads
|
||||||
import socket, struct, sys, signal, atexit, traceback
|
import socket, struct, sys, signal, atexit, traceback
|
||||||
import struct
|
import struct
|
||||||
|
import getpass
|
||||||
|
import shelve, anydbm
|
||||||
|
|
||||||
class SmartcardOS(object): # {{{
|
class SmartcardOS(object): # {{{
|
||||||
def __init__(self, filename,mf=None,ins2handler=None, maxle=MAX_SHORT_LE,sam=None):
|
def __init__(self, mf=None, sam=None, ins2handler=None, maxle=MAX_SHORT_LE):
|
||||||
self.config_key = "DUMMYKEYDUMMYKEY" #TODO: Let user enter password
|
from CardGenerator import generate_iso_card
|
||||||
self.filename = filename
|
|
||||||
self.mf = mf
|
self.mf = mf
|
||||||
self.SAM = sam
|
self.SAM = sam
|
||||||
|
|
||||||
if self.filename != None:
|
|
||||||
try:
|
|
||||||
self.load(filename,self.config_key)
|
|
||||||
except ValueError:
|
|
||||||
print "Failed to load configuration %s" % filename
|
|
||||||
|
|
||||||
if self.SAM == None:
|
if self.mf == None and self.SAM == None:
|
||||||
print "Using default SAM. Insecure!!!"
|
self.mf, self.SAM = generate_iso_card()
|
||||||
self.SAM = SAM("testconfig.sam",self.config_key) #FIXME: Replace by defaul_SAM()
|
|
||||||
if self.mf == None:
|
|
||||||
print "Using default MF."
|
|
||||||
self.mf = MF(filedescriptor=FDB["DF"], lifecycle=LCB["ACTIVATED"], dfname=None)
|
|
||||||
self.SAM.set_MF(self.mf)
|
|
||||||
|
|
||||||
# pgp directory
|
|
||||||
#self.mf.append(DF(parent=self.mf,
|
|
||||||
#fid=4, dfname='\xd2\x76\x00\x01\x24\x01', bertlv_data=[]))
|
|
||||||
# pkcs-15 directories
|
|
||||||
#self.mf.append(DF(parent=self.mf,
|
|
||||||
#fid=1, dfname='\xa0\x00\x00\x00\x01'))
|
|
||||||
#self.mf.append(DF(parent=self.mf,
|
|
||||||
#fid=2, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00'))
|
|
||||||
#self.mf.append(DF(parent=self.mf,
|
|
||||||
#fid=3, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00'))
|
|
||||||
|
|
||||||
#import imp, os.path
|
|
||||||
#SAM_config = os.path.join(os.path.dirname(imp.find_module("SmartcardSAM")[1]), "testconfig.sam") #Path to initial SAM configuration, stored on disk
|
|
||||||
|
|
||||||
if not ins2handler:
|
if not ins2handler:
|
||||||
self.ins2handler = {
|
self.ins2handler = {
|
||||||
@@ -108,42 +83,7 @@ class SmartcardOS(object): # {{{
|
|||||||
self.atr = SmartcardOS.makeATR(T=1, directConvention = True, TA1=0x13,
|
self.atr = SmartcardOS.makeATR(T=1, directConvention = True, TA1=0x13,
|
||||||
histChars = chr(0x80) + chr(0x70 + len(card_capabilities)) +
|
histChars = chr(0x80) + chr(0x70 + len(card_capabilities)) +
|
||||||
card_capabilities)
|
card_capabilities)
|
||||||
|
|
||||||
def save(self):
|
|
||||||
"""
|
|
||||||
Save the configuration of the current Smartcard (MF + SAM) to disk.
|
|
||||||
To files will be stored: <filename>.mf for the mf and <filename>.sam
|
|
||||||
for the SAM.
|
|
||||||
Both files will be encrypted.
|
|
||||||
"""
|
|
||||||
if self.filename == None:
|
|
||||||
raise ValueError, "No filename specified"
|
|
||||||
else:
|
|
||||||
mf = dumps(self.mf)
|
|
||||||
path = self.filename + ".mf"
|
|
||||||
mf = self.SAM.saveToDisk(mf,path)
|
|
||||||
path = self.filename + ".sam"
|
|
||||||
self.SAM.saveConfiguration(path)
|
|
||||||
#self.SAM.saveConfiguration(path,"DUMMYKEYDUMMYKEY")
|
|
||||||
|
|
||||||
def load(self,filename,password):
|
|
||||||
"""
|
|
||||||
Try to load a configuration from the filesystem. MF or SAM are only loaded if
|
|
||||||
they aren't yet specified.
|
|
||||||
"""
|
|
||||||
if self.SAM == None:
|
|
||||||
self.SAM = SAM("testconfig.sam","DUMMYKEYDUMMYKEY",None) #FIXME: replace by default_SAM()
|
|
||||||
path = filename + ".sam"
|
|
||||||
try:
|
|
||||||
self.SAM.loadConfiguration(path,password)
|
|
||||||
except IOError:
|
|
||||||
print "Failed to open %s" % path
|
|
||||||
if self.mf == None:
|
|
||||||
path = filename + ".mf"
|
|
||||||
data = self.SAM.loadFromDisk(path,password)
|
|
||||||
self.mf = loads(data)
|
|
||||||
print "Succesfully loaded MF from %s" % path
|
|
||||||
|
|
||||||
def powerUp(self):
|
def powerUp(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -363,72 +303,32 @@ class PassportOS(SmartcardOS):
|
|||||||
Basic Access Control (BAC) mechanisms.
|
Basic Access Control (BAC) mechanisms.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, filename,mf=None, ins2handler=None, maxle=MAX_SHORT_LE):
|
def __init__(self, mf=None, sam=None, ins2handler=None, maxle=MAX_SHORT_LE):
|
||||||
if filename == None and mf == None:
|
from CardGenerator import generate_ePass
|
||||||
mf = MF()
|
|
||||||
else:
|
if mf == None and sam == None:
|
||||||
pass #TODO: Load data from disk
|
mf, sam = generate_ePass()
|
||||||
self.generate_data_structure(mf)
|
else: #TODO: Sanity check if mf or sam are provided
|
||||||
self.SAM = PassportSAM(mf)
|
if mf == None:
|
||||||
SmartcardOS.__init__(self, None, mf=mf, ins2handler=ins2handler, maxle=maxle,sam=self.SAM)
|
mf, tmp = generate_ePass()
|
||||||
|
if sam == None:
|
||||||
def generate_data_structure(self,mf):
|
sam = PassportSAM(mf)
|
||||||
MRZ1 = "P<UTOERIKSSON<<ANNA<MARIX<<<<<<<<<<<<<<<<<<<"
|
SmartcardOS.__init__(self, mf=mf, sam=sam, ins2handler=ins2handler, maxle=maxle)
|
||||||
MRZ2 = "L898902C<3UTO6908061F9406236ZE184226B<<<<<14"
|
|
||||||
MRZ = MRZ1+MRZ2
|
|
||||||
from PIL import Image
|
|
||||||
picturepath = "jp2.jpg"
|
|
||||||
try:
|
|
||||||
im = Image.open(picturepath)
|
|
||||||
pic_width, pic_height = im.size
|
|
||||||
fd = open(picturepath,"rb")
|
|
||||||
picture = fd.read()
|
|
||||||
fd.close()
|
|
||||||
except IOError:
|
|
||||||
print "Could not find picture %s" % picturepath
|
|
||||||
pic_width = 0
|
|
||||||
pic_height = 0
|
|
||||||
picture = None
|
|
||||||
|
|
||||||
#We need a MF with Application DF \xa0\x00\x00\x02G\x10\x01
|
|
||||||
mf.append(DF(parent=mf,fid=4, dfname='\xa0\x00\x00\x02G\x10\x01', bertlv_data=[]))
|
|
||||||
df = mf.currentDF()
|
|
||||||
mf.append(TransparentStructureEF(parent=df, fid=0x011E, filedescriptor=0, data=""))#EF.COM
|
|
||||||
mf.append(TransparentStructureEF(parent=df, fid=0x0101, filedescriptor=0, data=""))#EF.DG1
|
|
||||||
mf.append(TransparentStructureEF(parent=df, fid=0x0102, filedescriptor=0, data=""))#EF.DG2
|
|
||||||
mf.append(TransparentStructureEF(parent=df, fid=0x010D, filedescriptor=0, data=""))#EF.SOD
|
|
||||||
#EF.COM
|
|
||||||
COM = pack([(0x5F01,4,"0107"),(0x5F36,6,"040000"),(0x5C,2,"6175")])
|
|
||||||
COM = pack(((0x60,len(COM),COM),))
|
|
||||||
fid = df.select("fid",0x011E)
|
|
||||||
fid.writebinary([0],[COM])
|
|
||||||
#EF.DG1
|
|
||||||
DG1 = pack([(0x5F1F,len(MRZ),MRZ)])
|
|
||||||
DG1 = pack([(0x61,len(DG1),DG1)])
|
|
||||||
fid = df.select("fid",0x0101)
|
|
||||||
fid.writebinary([0],[DG1])
|
|
||||||
#EF.DG2
|
|
||||||
if picture != None:
|
|
||||||
IIB = "\x00\x01" + inttostring(pic_width,2) + inttostring(pic_height,2) + 6 * "\x00"
|
|
||||||
length = 32 + len(picture) #32 is the length of IIB + FIB
|
|
||||||
FIB = inttostring(length,4) + 16 * "\x00"
|
|
||||||
FRH = "FAC" + "\x00" + "010" + "\x00" + inttostring(14+length,4) + inttostring(1,2)
|
|
||||||
picture = FRH + FIB + IIB + picture
|
|
||||||
DG2 = pack([(0xA1,8,"\x87\x02\x01\x01\x88\x02\x05\x01"),(0x5F2E,len(picture),picture)])
|
|
||||||
DG2 = pack([(0x02,1,"\x01"),(0x7F60,len(DG2),DG2)])
|
|
||||||
DG2 = pack([(0x7F61,len(DG2),DG2)])
|
|
||||||
fid = df.select("fid",0x0102)
|
|
||||||
fid.writebinary([0],[DG2])
|
|
||||||
|
|
||||||
class CryptoflexOS(SmartcardOS): # {{{
|
class CryptoflexOS(SmartcardOS): # {{{
|
||||||
def __init__(self, filename, mf=None, ins2handler=None, maxle=MAX_SHORT_LE):
|
def __init__(self, mf=None, sam=None, ins2handler=None, maxle=MAX_SHORT_LE):
|
||||||
if filename == None and mf == None:
|
from GenerateCard import generate_cryptoflex
|
||||||
mf = CryptoflexMF()
|
|
||||||
mf.append(TransparentStructureEF(parent=mf, fid=0x0002, filedescriptor=0x01,
|
if mf == None and sam == None:
|
||||||
data="\x00\x00\x00\x01\x00\x01\x00\x00"))#EF.ICCSN
|
mf, sam = generate_cryptoflex()
|
||||||
#TODO: Load objects from disk
|
else:
|
||||||
SmartcardOS.__init__(self, None,mf=mf, ins2handler=ins2handler, maxle=maxle,
|
if mf == None:
|
||||||
sam=CryptoflexSAM("testconfig.sam","DUMMYKEYDUMMYKEY"))
|
mf, tmp = generate_cryptoflex()
|
||||||
|
del tmp
|
||||||
|
if sam == None:
|
||||||
|
sam = CryptoflexSAM(mf)
|
||||||
|
|
||||||
|
SmartcardOS.__init__(self, mf, sam, ins2handler, maxle)
|
||||||
self.atr = '\x3B\xE2\x00\x00\x40\x20\x49\x06'
|
self.atr = '\x3B\xE2\x00\x00\x40\x20\x49\x06'
|
||||||
|
|
||||||
def execute(self, msg):
|
def execute(self, msg):
|
||||||
@@ -484,29 +384,56 @@ VPCD_CTRL_RESET = 2
|
|||||||
VPCD_CTRL_ATR = 4
|
VPCD_CTRL_ATR = 4
|
||||||
|
|
||||||
class VirtualICC(object): # {{{
|
class VirtualICC(object): # {{{
|
||||||
|
|
||||||
def __init__(self, filename,os=None, lenlen=3, host="localhost", port=35963):
|
def __init__(self, filename, type, lenlen=3, host="localhost", port=35963):
|
||||||
if os == None:
|
from os.path import exists
|
||||||
self.os = SmartcardOS(filename)
|
from CardGenerator import loadCard
|
||||||
|
|
||||||
|
self.filename = None
|
||||||
|
SAM = None
|
||||||
|
MF = None
|
||||||
|
|
||||||
|
#If a filename is specified, try to load the card from disk
|
||||||
|
if filename == None:
|
||||||
|
print "No filename specified. The card will not be safed!"
|
||||||
else:
|
else:
|
||||||
self.os = os
|
self.filename = filename
|
||||||
self.filename = filename
|
if exists(filename):
|
||||||
self.sock = self.connectToPort(host, port)
|
print "Found existing card " + filename + ". Will try to open it"
|
||||||
self.sock.settimeout(None)
|
SAM, MF = loadCard(filename)
|
||||||
|
else:
|
||||||
|
print "No file " + filename + " found. Will create new file at termination of program."
|
||||||
|
|
||||||
|
#Generate an OS object of the correct type
|
||||||
|
if type == "iso7816":
|
||||||
|
self.os = SmartcardOS(MF, SAM)
|
||||||
|
elif type == "ePass":
|
||||||
|
self.os = PassportOS(MF, SAM)
|
||||||
|
elif type == "cryptoflex":
|
||||||
|
self.os = CryptoflexOS(MF, SAM)
|
||||||
|
else:
|
||||||
|
print "Unknown cardtype " + type + ". Will use standard ISO 7816 cardtype"
|
||||||
|
type = "iso7816"
|
||||||
|
self.os = SmartcardOS(MF, SAM)
|
||||||
|
self.type = type
|
||||||
|
|
||||||
|
#Connect to the VPCD
|
||||||
|
try:
|
||||||
|
self.sock = self.connectToPort(host, port)
|
||||||
|
self.sock.settimeout(None)
|
||||||
|
except socket.error as e:
|
||||||
|
print "Failed to open socket: " + str(e) + ". Is pcscd running? Is vpcd installed?"
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
self.lenlen = lenlen
|
self.lenlen = lenlen
|
||||||
signal.signal(signal.SIGINT, self.signalHandler)
|
signal.signal(signal.SIGINT, self.signalHandler)
|
||||||
atexit.register(self.signalHandler)
|
#atexit.register(self.signalHandler)
|
||||||
atexit.register(self.saveCard)
|
#atexit.register(saveCard)
|
||||||
|
|
||||||
def saveCard(self):
|
|
||||||
if self.filename != None:
|
|
||||||
print "Saving smartcard configuration to %s" % self.filename
|
|
||||||
self.os.save()
|
|
||||||
else:
|
|
||||||
print "No filename specified, card configuration will not be saved."
|
|
||||||
|
|
||||||
def signalHandler(self, signal=None, frame=None):
|
def signalHandler(self, signal=None, frame=None):
|
||||||
|
from CardGenerator import saveCard
|
||||||
self.sock.close()
|
self.sock.close()
|
||||||
|
saveCard(self.os.mf, self.os.SAM, self.filename)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -567,11 +494,6 @@ if __name__ == "__main__":
|
|||||||
dest="filename", default=None,
|
dest="filename", default=None,
|
||||||
help="Name of a smartcard stored in the filesystem. The card will be loaded")
|
help="Name of a smartcard stored in the filesystem. The card will be loaded")
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
if options.type == 'cryptoflex':
|
|
||||||
vicc = VirtualICC(options.filename,os=CryptoflexOS(options.filename))
|
vicc = VirtualICC(options.filename, options.type)
|
||||||
elif options.type == 'ePass':
|
|
||||||
#raise ValueError, "Not implemented yet!"
|
|
||||||
vicc = VirtualICC(options.filename,os=PassportOS(options.filename))
|
|
||||||
else:
|
|
||||||
vicc = VirtualICC(options.filename)
|
|
||||||
vicc.run()
|
vicc.run()
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
$p5k2$$RGHiUkhA$<24>U<EFBFBD>x<01>U<EFBFBD><55><EFBFBD><EFBFBD><EFBFBD>3I^낎P(<28>f<EFBFBD>Kc<4B><63>iveY/0<><30><EFBFBD><EFBFBD>l<EFBFBD><6C>h<EFBFBD><05>{?<0E>gy<67><79><EFBFBD><EFBFBD>a<EFBFBD><61><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>P<><50><EFBFBD><EFBFBD><12>?Z<><1F>U!<21><>o<EFBFBD><6F>KΦP<CEA6>,<2C><><EFBFBD><EFBFBD>c<63>o<EFBFBD>{0F<30>\<5C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>g#<23><>~<7E><>r˹<72>l<EFBFBD>/}<7D>l<EFBFBD><6C>3<19><><EFBFBD>#<23><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>%<06>ѱ@<40>OG
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
$p5k2$$kj3EFei1$F<>M?<3F><><EFBFBD>?06ՋD<D58B>A֥
|
|
||||||
#6$<24>2vz<76><7A>t<EFBFBD>(<28>\rCiKs<4B><73><EFBFBD>:<3A>8<EFBFBD>D<03><>DF<44>poR<6F><52>+<<3C>Y2-<2D>kF<6B><46>$<24><><EFBFBD>C<EFBFBD>C߯<><16>'<27><>#,<2C>
|
|
||||||
Reference in New Issue
Block a user