Remove serialization support

This commit is contained in:
Dominik
2016-04-10 19:31:24 +02:00
parent cb0224edfc
commit 41bd7ab105
7 changed files with 11 additions and 259 deletions

View File

@@ -152,10 +152,9 @@ if args.reversed:
else:
hostname = args.hostname
vicc = VirtualICC(args.file, args.datasetfile, args.type,
hostname, args.port, readernum=args.reader,
ef_cardaccess=ef_cardaccess_data, ef_cardsecurity=ef_cardsecurity_data,
ca_key=ca_key_data, cvca=cvca, disable_checks=args.disable_ta_checks,
esign_ca_cert=esign_ca_cert, esign_cert=esign_cert,
logginglevel=logginglevel)
vicc = VirtualICC(args.datasetfile, args.type, hostname, args.port,
readernum=args.reader, ef_cardaccess=ef_cardaccess_data,
ef_cardsecurity=ef_cardsecurity_data, ca_key=ca_key_data, cvca=cvca,
disable_checks=args.disable_ta_checks, esign_ca_cert=esign_ca_cert,
esign_cert=esign_cert, logginglevel=logginglevel)
vicc.run()

View File

@@ -17,7 +17,6 @@
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
import anydbm
import binascii
import getpass
import logging
@@ -32,7 +31,6 @@ from virtualsmartcard.TLVutils import pack
from virtualsmartcard.utils import inttostring
from virtualsmartcard.SmartcardFilesystem import MF, DF, TransparentStructureEF
from virtualsmartcard.ConstantDefinitions import FDB, ALGO_MAPPING
from virtualsmartcard.CryptoUtils import protect_string, read_protected_string
from virtualsmartcard.SmartcardSAM import SAM
# pgp directory
@@ -693,50 +691,6 @@ class CardGenerator(object):
if sam is not None:
self.sam = sam
def loadCard(self, filename):
"""Load a card from disk"""
if self.password is None:
self.password = getpass.getpass("Please enter your password:")
db = anydbm.open(filename, 'r')
try:
serializedMF = read_protected_string(db["mf"], self.password)
serializedSAM = read_protected_string(db["sam"], self.password)
self.type = db["type"]
finally:
db.close()
self.sam = loads(serializedSAM)
self.mf = loads(serializedMF)
def saveCard(self, filename):
"""Save the currently running card to disk"""
if self.password is None:
passwd1 = getpass.getpass("Please enter your password:")
passwd2 = getpass.getpass("Please retype your password:")
if (passwd1 != passwd2):
raise ValueError("Passwords did not match. Will now exit")
else:
self.password = passwd1
if self.mf is None or self.sam is None:
raise ValueError("Card Generator wasn't set up properly" +
"(missing MF or SAM).")
mf_string = dumps(self.mf)
sam_string = dumps(self.sam)
protectedMF = protect_string(mf_string, self.password)
protectedSAM = protect_string(sam_string, self.password)
db = anydbm.open(filename, 'c')
try:
db["mf"] = protectedMF
db["sam"] = protectedSAM
db["type"] = self.type
db["version"] = "0.1"
finally:
db.close()
def readDatagroups(self, datasetfile):
"""Read Datagroups from file"""
with open(datasetfile, 'r') as f:
@@ -759,15 +713,7 @@ if __name__ == "__main__":
default='iso7816',
choices=['iso7816', 'cryptoflex', 'ePass', 'nPA'],
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 is None:
logging.error("You have to provide a filename using the -f option")
sys.exit()
generator = CardGenerator(options.type)
generator.generateCard()
generator.saveCard(options.filename)

View File

@@ -21,7 +21,6 @@ import logging
import re
from struct import pack
from base64 import b64encode
from hashlib import pbkdf2_hmac
from random import randint
from virtualsmartcard.utils import inttostring
@@ -217,72 +216,6 @@ def operation_on_string(string1, string2, op):
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\$(\d*)\$([\w./]*)\$([\w./]*)')
match = regex.match(pbk)
if match is not None:
iterations = match.group(1)
salt = match.group(2)
key = match.group(3)
else:
raise ValueError
# Encrypt the string, authenticate and format it
if cipherspec is None:
cipherspec = "AES-CBC"
else:
pass # TODO: Sanity check for cipher
blocklength = get_cipher_blocklen(cipherspec)
paddedString = append_padding(blocklength, 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./]*)\$')
match = regex.match(string)
if match is not None:
salt = match.group(1)
crypted = string[match.end():len(string) - hmac_length]
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:
logging.error("Found HMAC %s expected %s" % (str(hmac), str(checksum)))
raise ValueError("Failed to authenticate data. Wrong password?")
# Decrypt data
if cipherspec is None:
cipherspec = "AES-CBC"
decrypted = cipher(False, cipherspec, key, crypted)
return strip_padding(get_cipher_blocklen(cipherspec), decrypted)
# *******************************************************************
# * Cyberflex specific methods *
# *******************************************************************
@@ -297,62 +230,6 @@ def calculate_MAC(session_key, message, iv=CYBERFLEX_IV):
return crypted[len(padded) - cipher.block_size:]
def crypt(word, salt=None, iterations=None):
"""PBKDF2-based unix crypt(3) replacement.
The number of iterations specified in the salt overrides the 'iterations'
parameter.
The effective hash length is 192 bits.
"""
# Generate a (pseudo-)random salt if the user hasn't provided one.
if salt is None:
salt = _makesalt()
# salt must be a string or the us-ascii subset of unicode
if isinstance(salt, unicode):
salt = salt.encode("us-ascii")
if not isinstance(salt, str):
raise TypeError("salt must be a string")
# word must be a string or unicode (in the latter case, we convert to
# UTF-8)
if isinstance(word, unicode):
word = word.encode("UTF-8")
if not isinstance(word, str):
raise TypeError("word must be a string or unicode")
# Try to extract the real salt and iteration count from the salt
if salt.startswith("$p5k2$"):
(iterations, salt, dummy) = salt.split("$")[2:5]
if iterations == "":
iterations = 400
else:
converted = int(iterations, 16)
if iterations != "%x" % converted: # lowercase hex, minimum digits
raise ValueError("Invalid salt")
iterations = converted
if not (iterations >= 1):
raise ValueError("Invalid salt")
# Make sure the salt matches the allowed character set
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678"\
"9./"
for ch in salt:
if ch not in allowed:
raise ValueError("Illegal character %r in salt" % (ch,))
if iterations is None or iterations == 400:
iterations = 400
salt = "$p5k2$$" + salt
else:
salt = "$p5k2$%x$%s" % (iterations, salt)
rawhash = pbkdf2_hmac('sha1', salt, word, iterations, 24)
return salt + "$" + b64encode(rawhash, "./")
def _makesalt():
"""Return a 48-bit pseudorandom salt for crypt().

View File

@@ -387,7 +387,7 @@ class VirtualICC(object):
the vpcd, which forwards it to the application.
"""
def __init__(self, filename, datasetfile, card_type, host, port,
def __init__(self, datasetfile, card_type, host, port,
readernum=None, ef_cardsecurity=None, ef_cardaccess=None,
ca_key=None, cvca=None, disable_checks=False, esign_key=None,
esign_ca_cert=None, esign_cert=None,
@@ -398,18 +398,8 @@ class VirtualICC(object):
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%d.%m.%Y %H:%M:%S")
self.filename = None
self.cardGenerator = CardGenerator(card_type)
# If a filename is specified, try to load the card from disk
if filename is not None:
self.filename = filename
if exists(filename):
self.cardGenerator.loadCard(self.filename)
else:
logging.info("Creating new card which will be saved in %s.",
self.filename)
# If a dataset file is specified, read the card's data groups from disk
if datasetfile is not None:
if exists(datasetfile):
@@ -572,6 +562,3 @@ class VirtualICC(object):
self.sock.close()
if self.server_sock:
self.server_sock.close()
if self.filename is not None:
self.cardGenerator.setCard(self.os.mf, self.os.SAM)
self.cardGenerator.saveCard(self.filename)

View File

@@ -17,7 +17,6 @@
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
import anydbm
import os
import tempfile
import unittest
@@ -30,32 +29,13 @@ class ISO7816GeneratorTest(unittest.TestCase):
card_type = 'iso7816'
def setUp(self):
self.filename = tempfile.mktemp()
self.card_generator = CardGenerator(self.card_type)
self.card_generator.password = "TestPassword"
def test_card_creation(self):
self.card_generator.generateCard()
self.card_generator.saveCard(self.filename)
mf, sam = self.card_generator.getCard()
self.assertIsNotNone(mf)
self.assertIsNotNone(sam)
os.unlink(self.filename)
def test_load_card_from_file(self):
self.card_generator.generateCard()
self.card_generator.saveCard(self.filename)
local_generator = CardGenerator(self.card_type)
local_generator.password = self.card_generator.password
local_generator.loadCard(self.filename)
mf, sam = local_generator.getCard()
self.assertIsNotNone(mf)
self.assertIsNotNone(sam)
os.unlink(self.filename)
def test_load_nonexistent_file(self):
with self.assertRaises(anydbm.error):
self.card_generator.loadCard(self.filename)
def test_get_and_set_card(self):
self.card_generator.generateCard()
@@ -69,9 +49,7 @@ class TestNPACardGenerator(ISO7816GeneratorTest):
card_type = 'nPA'
def setUp(self):
self.filename = tempfile.mktemp()
self.card_generator = CardGenerator(self.card_type)
self.card_generator.password = "TestPassword"
self.test_readDatagroups_file = "/../../../../npa-example-data/"\
"Example_Dataset_Mueller_Gertrud.txt"

View File

@@ -26,46 +26,11 @@ class TestCryptoUtils(unittest.TestCase):
def setUp(self):
self.teststring = "DEADBEEFistatsyksdvhwohfwoehcowc8hw8rogfq8whv75tsg"\
"ohsav8wress"
self.testpass = "SomeRandomPassphrase"
# The following string was generated using the proteced string method
# and is used as a regression test.
# The data generated by protect_string should actually consist of
# printable characters only but that would break backwards
# compatibility with the (buggy) legacy implementation
self.protectedTestString = "2470356b32242478424f50746f6d712448b8f6285"\
"ac8462fffc6aef921f2ad84855219c5aaafb39c4c"\
"c9e54d1634c60cfc9347c67fa55967c5b0130469c"\
"96a44f0b73c53f5ddfc43cd8c1ef68965ebb23330"\
"39326538373233393735346565383864313536383"\
"0666637336134316532".decode('hex')
self.salt = "POcwYIHr"
self.cryptedWord = "$p5k2$$POcwYIHr$SPaWqD3NpmLZc6gXbeybnAoCxo7Oc//K"
self.cryptedWordThousandIterations = "$p5k2$3e8$POcwYIHr$f/mEOCulo6v7"\
"Nq2ooS3480xTet6zdGbI"
def test_padding(self):
padded = append_padding(16, self.teststring)
unpadded = strip_padding(16, padded)
self.assertEqual(unpadded, self.teststring)
def test_protect_string(self):
protectedString = protect_string(self.teststring, self.testpass)
unprotectedString = read_protected_string(protectedString,
self.testpass)
self.assertEqual(self.teststring, unprotectedString)
def test_unprotect_string(self):
unprotectedString = read_protected_string(self.protectedTestString,
self.testpass)
self.assertEqual(unprotectedString, self.teststring)
def test_crypt(self):
cryptedWord = crypt(self.teststring, self.salt)
self.assertEqual(cryptedWord, self.cryptedWord)
def test_crypt_non_default_iterations(self):
cryptedWord = crypt(self.teststring, self.salt, 1000)
self.assertEqual(cryptedWord, self.cryptedWordThousandIterations)
if __name__ == "__main__":
unittest.main()