Merge pull request #78 from d0/no_serialization
Remove serialization support
This commit is contained in:
@@ -2,19 +2,19 @@
|
||||
#
|
||||
# Copyright (C) 2010-2012 Frank Morgner <frankmorgner@gmail.com>
|
||||
# Copyright (C) 2010 Dominik Oepen <oepen@informatik.hu-berlin.de>
|
||||
#
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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().
|
||||
|
||||
|
||||
@@ -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):
|
||||
@@ -584,6 +574,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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user