Remove serialization support
This commit is contained in:
Submodule npa/src/opensc updated: 34a69aeac8...db60f8da59
@@ -152,10 +152,9 @@ if args.reversed:
|
|||||||
else:
|
else:
|
||||||
hostname = args.hostname
|
hostname = args.hostname
|
||||||
|
|
||||||
vicc = VirtualICC(args.file, args.datasetfile, args.type,
|
vicc = VirtualICC(args.datasetfile, args.type, hostname, args.port,
|
||||||
hostname, args.port, readernum=args.reader,
|
readernum=args.reader, ef_cardaccess=ef_cardaccess_data,
|
||||||
ef_cardaccess=ef_cardaccess_data, ef_cardsecurity=ef_cardsecurity_data,
|
ef_cardsecurity=ef_cardsecurity_data, ca_key=ca_key_data, cvca=cvca,
|
||||||
ca_key=ca_key_data, cvca=cvca, disable_checks=args.disable_ta_checks,
|
disable_checks=args.disable_ta_checks, esign_ca_cert=esign_ca_cert,
|
||||||
esign_ca_cert=esign_ca_cert, esign_cert=esign_cert,
|
esign_cert=esign_cert, logginglevel=logginglevel)
|
||||||
logginglevel=logginglevel)
|
|
||||||
vicc.run()
|
vicc.run()
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
|
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
import anydbm
|
|
||||||
import binascii
|
import binascii
|
||||||
import getpass
|
import getpass
|
||||||
import logging
|
import logging
|
||||||
@@ -32,7 +31,6 @@ from virtualsmartcard.TLVutils import pack
|
|||||||
from virtualsmartcard.utils import inttostring
|
from virtualsmartcard.utils import inttostring
|
||||||
from virtualsmartcard.SmartcardFilesystem import MF, DF, TransparentStructureEF
|
from virtualsmartcard.SmartcardFilesystem import MF, DF, TransparentStructureEF
|
||||||
from virtualsmartcard.ConstantDefinitions import FDB, ALGO_MAPPING
|
from virtualsmartcard.ConstantDefinitions import FDB, ALGO_MAPPING
|
||||||
from virtualsmartcard.CryptoUtils import protect_string, read_protected_string
|
|
||||||
from virtualsmartcard.SmartcardSAM import SAM
|
from virtualsmartcard.SmartcardSAM import SAM
|
||||||
|
|
||||||
# pgp directory
|
# pgp directory
|
||||||
@@ -693,50 +691,6 @@ class CardGenerator(object):
|
|||||||
if sam is not None:
|
if sam is not None:
|
||||||
self.sam = sam
|
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):
|
def readDatagroups(self, datasetfile):
|
||||||
"""Read Datagroups from file"""
|
"""Read Datagroups from file"""
|
||||||
with open(datasetfile, 'r') as f:
|
with open(datasetfile, 'r') as f:
|
||||||
@@ -759,15 +713,7 @@ if __name__ == "__main__":
|
|||||||
default='iso7816',
|
default='iso7816',
|
||||||
choices=['iso7816', 'cryptoflex', 'ePass', 'nPA'],
|
choices=['iso7816', 'cryptoflex', 'ePass', 'nPA'],
|
||||||
help="Type of Smartcard [default: %default]")
|
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()
|
(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 = CardGenerator(options.type)
|
||||||
generator.generateCard()
|
generator.generateCard()
|
||||||
generator.saveCard(options.filename)
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import logging
|
|||||||
import re
|
import re
|
||||||
from struct import pack
|
from struct import pack
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from hashlib import pbkdf2_hmac
|
|
||||||
from random import randint
|
from random import randint
|
||||||
from virtualsmartcard.utils import inttostring
|
from virtualsmartcard.utils import inttostring
|
||||||
|
|
||||||
@@ -217,72 +216,6 @@ def operation_on_string(string1, string2, op):
|
|||||||
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\$(\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 *
|
# * Cyberflex specific methods *
|
||||||
# *******************************************************************
|
# *******************************************************************
|
||||||
@@ -297,62 +230,6 @@ def calculate_MAC(session_key, message, iv=CYBERFLEX_IV):
|
|||||||
|
|
||||||
return crypted[len(padded) - cipher.block_size:]
|
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():
|
def _makesalt():
|
||||||
"""Return a 48-bit pseudorandom salt for crypt().
|
"""Return a 48-bit pseudorandom salt for crypt().
|
||||||
|
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ class VirtualICC(object):
|
|||||||
the vpcd, which forwards it to the application.
|
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,
|
readernum=None, ef_cardsecurity=None, ef_cardaccess=None,
|
||||||
ca_key=None, cvca=None, disable_checks=False, esign_key=None,
|
ca_key=None, cvca=None, disable_checks=False, esign_key=None,
|
||||||
esign_ca_cert=None, esign_cert=None,
|
esign_ca_cert=None, esign_cert=None,
|
||||||
@@ -398,18 +398,8 @@ class VirtualICC(object):
|
|||||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||||
datefmt="%d.%m.%Y %H:%M:%S")
|
datefmt="%d.%m.%Y %H:%M:%S")
|
||||||
|
|
||||||
self.filename = None
|
|
||||||
self.cardGenerator = CardGenerator(card_type)
|
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 a dataset file is specified, read the card's data groups from disk
|
||||||
if datasetfile is not None:
|
if datasetfile is not None:
|
||||||
if exists(datasetfile):
|
if exists(datasetfile):
|
||||||
@@ -572,6 +562,3 @@ class VirtualICC(object):
|
|||||||
self.sock.close()
|
self.sock.close()
|
||||||
if self.server_sock:
|
if self.server_sock:
|
||||||
self.server_sock.close()
|
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/>.
|
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
import anydbm
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
@@ -30,32 +29,13 @@ class ISO7816GeneratorTest(unittest.TestCase):
|
|||||||
card_type = 'iso7816'
|
card_type = 'iso7816'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.filename = tempfile.mktemp()
|
|
||||||
self.card_generator = CardGenerator(self.card_type)
|
self.card_generator = CardGenerator(self.card_type)
|
||||||
self.card_generator.password = "TestPassword"
|
|
||||||
|
|
||||||
def test_card_creation(self):
|
def test_card_creation(self):
|
||||||
self.card_generator.generateCard()
|
self.card_generator.generateCard()
|
||||||
self.card_generator.saveCard(self.filename)
|
|
||||||
mf, sam = self.card_generator.getCard()
|
mf, sam = self.card_generator.getCard()
|
||||||
self.assertIsNotNone(mf)
|
self.assertIsNotNone(mf)
|
||||||
self.assertIsNotNone(sam)
|
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):
|
def test_get_and_set_card(self):
|
||||||
self.card_generator.generateCard()
|
self.card_generator.generateCard()
|
||||||
@@ -69,9 +49,7 @@ class TestNPACardGenerator(ISO7816GeneratorTest):
|
|||||||
card_type = 'nPA'
|
card_type = 'nPA'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.filename = tempfile.mktemp()
|
|
||||||
self.card_generator = CardGenerator(self.card_type)
|
self.card_generator = CardGenerator(self.card_type)
|
||||||
self.card_generator.password = "TestPassword"
|
|
||||||
self.test_readDatagroups_file = "/../../../../npa-example-data/"\
|
self.test_readDatagroups_file = "/../../../../npa-example-data/"\
|
||||||
"Example_Dataset_Mueller_Gertrud.txt"
|
"Example_Dataset_Mueller_Gertrud.txt"
|
||||||
|
|
||||||
|
|||||||
@@ -26,46 +26,11 @@ class TestCryptoUtils(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.teststring = "DEADBEEFistatsyksdvhwohfwoehcowc8hw8rogfq8whv75tsg"\
|
self.teststring = "DEADBEEFistatsyksdvhwohfwoehcowc8hw8rogfq8whv75tsg"\
|
||||||
"ohsav8wress"
|
"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):
|
def test_padding(self):
|
||||||
padded = append_padding(16, self.teststring)
|
padded = append_padding(16, self.teststring)
|
||||||
unpadded = strip_padding(16, padded)
|
unpadded = strip_padding(16, padded)
|
||||||
self.assertEqual(unpadded, self.teststring)
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user