summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathaniel McCallum <npmccallum@redhat.com>2015-08-31 10:46:19 -0400
committerMartin Basti <mbasti@redhat.com>2017-03-01 12:51:50 +0100
commitd00ae870dda2889545c9d93e82e44526bfd4f431 (patch)
treef068d2206b2ae6cb614a0f5a12e2b091b53f26eb
parent3be696c92f6948ea0ced9784920600b73703e414 (diff)
downloadfreeipa-d00ae870dda2889545c9d93e82e44526bfd4f431.tar.gz
freeipa-d00ae870dda2889545c9d93e82e44526bfd4f431.tar.xz
freeipa-d00ae870dda2889545c9d93e82e44526bfd4f431.zip
Migrate OTP import script to python-cryptography
https://fedorahosted.org/freeipa/ticket/5192 Reviewed-By: Stanislav Laznicka <slaznick@redhat.com>
-rw-r--r--ipaserver/install/ipa_otptoken_import.py104
-rw-r--r--ipatests/test_ipaserver/test_otptoken_import.py100
2 files changed, 80 insertions, 124 deletions
diff --git a/ipaserver/install/ipa_otptoken_import.py b/ipaserver/install/ipa_otptoken_import.py
index 552fdfb03..3b2e52ab2 100644
--- a/ipaserver/install/ipa_otptoken_import.py
+++ b/ipaserver/install/ipa_otptoken_import.py
@@ -29,11 +29,15 @@ import struct
from lxml import etree
import dateutil.parser
import dateutil.tz
-import nss.nss as nss
import gssapi
import six
from six.moves import xrange
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.kdf import pbkdf2
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+from cryptography.hazmat.backends import default_backend
+
from ipaplatform.paths import paths
from ipapython import admintool
from ipalib import api, errors
@@ -119,13 +123,13 @@ def convertAlgorithm(value):
"Converts encryption URI to (mech, ivlen)."
return {
- "http://www.w3.org/2001/04/xmlenc#aes128-cbc": (nss.CKM_AES_CBC_PAD, 128),
- "http://www.w3.org/2001/04/xmlenc#aes192-cbc": (nss.CKM_AES_CBC_PAD, 192),
- "http://www.w3.org/2001/04/xmlenc#aes256-cbc": (nss.CKM_AES_CBC_PAD, 256),
- "http://www.w3.org/2001/04/xmlenc#tripledes-cbc": (nss.CKM_DES3_CBC_PAD, 64),
- "http://www.w3.org/2001/04/xmldsig-more#camellia128": (nss.CKM_CAMELLIA_CBC_PAD, 128),
- "http://www.w3.org/2001/04/xmldsig-more#camellia192": (nss.CKM_CAMELLIA_CBC_PAD, 192),
- "http://www.w3.org/2001/04/xmldsig-more#camellia256": (nss.CKM_CAMELLIA_CBC_PAD, 256),
+ "http://www.w3.org/2001/04/xmlenc#aes128-cbc": (algorithms.AES, modes.CBC, 128),
+ "http://www.w3.org/2001/04/xmlenc#aes192-cbc": (algorithms.AES, modes.CBC, 192),
+ "http://www.w3.org/2001/04/xmlenc#aes256-cbc": (algorithms.AES, modes.CBC, 256),
+ "http://www.w3.org/2001/04/xmlenc#tripledes-cbc": (algorithms.TripleDES, modes.CBC, 64),
+ "http://www.w3.org/2001/04/xmldsig-more#camellia128": (algorithms.Camellia, modes.CBC, 128),
+ "http://www.w3.org/2001/04/xmldsig-more#camellia192": (algorithms.Camellia, modes.CBC, 192),
+ "http://www.w3.org/2001/04/xmldsig-more#camellia256": (algorithms.Camellia, modes.CBC, 256),
# TODO: add support for these formats.
# "http://www.w3.org/2001/04/xmlenc#kw-aes128": "kw-aes128",
@@ -135,7 +139,7 @@ def convertAlgorithm(value):
# "http://www.w3.org/2001/04/xmldsig-more#kw-camellia128": "kw-camellia128",
# "http://www.w3.org/2001/04/xmldsig-more#kw-camellia192": "kw-camellia192",
# "http://www.w3.org/2001/04/xmldsig-more#kw-camellia256": "kw-camellia256",
- }.get(value.lower(), (None, None))
+ }.get(value.lower(), (None, None, None))
def convertEncrypted(value, decryptor=None, pconv=base64.b64decode, econv=lambda x: x):
@@ -170,50 +174,29 @@ class PBKDF2KeyDerivation(XMLKeyDerivation):
if params is None:
raise ValueError("XML file is missing PBKDF2 parameters!")
- self.salt = fetch(params, "./xenc11:Salt/xenc11:Specified/text()", base64.b64decode)
- self.iter = fetch(params, "./xenc11:IterationCount/text()", int)
- self.klen = fetch(params, "./xenc11:KeyLength/text()", int)
- self.hmod = fetch(params, "./xenc11:PRF/@Algorithm", convertHMACType, hashlib.sha1)
+ salt = fetch(params, "./xenc11:Salt/xenc11:Specified/text()", base64.b64decode)
+ itrs = fetch(params, "./xenc11:IterationCount/text()", int)
+ klen = fetch(params, "./xenc11:KeyLength/text()", int)
+ hmod = fetch(params, "./xenc11:PRF/@Algorithm", convertHMACType, hashlib.sha1)
- if self.salt is None:
+ if salt is None:
raise ValueError("XML file is missing PBKDF2 salt!")
- if self.iter is None:
+ if itrs is None:
raise ValueError("XML file is missing PBKDF2 iteration count!")
- if self.klen is None:
+ if klen is None:
raise ValueError("XML file is missing PBKDF2 key length!")
- def derive(self, masterkey):
- mac = hmac.HMAC(masterkey, None, self.hmod)
-
- # Figure out how many blocks we will have to combine
- # to expand the master key to the desired length.
- blocks = self.klen // mac.digest_size
- if self.klen % mac.digest_size != 0:
- blocks += 1
-
- # Loop through each block adding it to the derived key.
- dk = []
- for i in range(1, blocks + 1):
- # Set initial values.
- last = self.salt + struct.pack('>I', i)
- hash = [0] * mac.digest_size
+ self.kdf = pbkdf2.PBKDF2HMAC(
+ algorithm=hmod,
+ length=klen,
+ salt=salt,
+ iterations=itrs
+ )
- # Perform n iterations.
- for _j in xrange(self.iter):
- tmp = mac.copy()
- tmp.update(last)
- last = tmp.digest()
-
- # XOR the previous hash with the new hash.
- for k in range(mac.digest_size):
- hash[k] ^= ord(last[k])
-
- # Add block to derived key.
- dk.extend(hash)
-
- return ''.join([chr(c) for c in dk])[:self.klen]
+ def derive(self, masterkey):
+ return self.kdf.derive(masterkey)
def convertKeyDerivation(value):
@@ -230,13 +213,17 @@ class XMLDecryptor(object):
* RFC 6931"""
def __init__(self, key, hmac=None):
- self.__key = nss.SecItem(key)
+ self.__key = key
self.__hmac = hmac
def __call__(self, element, mac=None):
- (mech, ivlen) = fetch(element, "./xenc:EncryptionMethod/@Algorithm", convertAlgorithm)
+ (algo, mode, klen) = fetch(element, "./xenc:EncryptionMethod/@Algorithm", convertAlgorithm)
data = fetch(element, "./xenc:CipherData/xenc:CipherValue/text()", base64.b64decode)
+ # Make sure the key is the right length.
+ if len(self.__key) * 8 != klen:
+ raise ValidationError("Invalid key length!")
+
# If a MAC is present, perform validation.
if mac:
tmp = self.__hmac.copy()
@@ -244,13 +231,14 @@ class XMLDecryptor(object):
if tmp.digest() != mac:
raise ValidationError("MAC validation failed!")
- # Decrypt the data.
- slot = nss.get_best_slot(mech)
- key = nss.import_sym_key(slot, mech, nss.PK11_OriginUnwrap, nss.CKA_ENCRYPT, self.__key)
- iv = nss.param_from_iv(mech, nss.SecItem(data[0:ivlen//8]))
- ctx = nss.create_context_by_sym_key(mech, nss.CKA_DECRYPT, key, iv)
- out = ctx.cipher_op(data[ivlen // 8:])
- out += ctx.digest_final()
+ iv = data[:algo.block_size / 8]
+ data = data[len(iv):]
+
+ cipher = Cipher(algo(self.__key), mode(iv))
+ decryptor = cipher.decryptor()
+
+ out = decryptor.update(data)
+ out += decryptor.finalize()
return out
@@ -470,14 +458,6 @@ class OTPTokenImport(admintool.AdminTool):
usage = "%prog [options] <PSKC file> <output file>"
@classmethod
- def main(cls, argv):
- nss.nss_init_nodb()
- try:
- super(OTPTokenImport, cls).main(argv)
- finally:
- nss.nss_shutdown()
-
- @classmethod
def add_options(cls, parser):
super(OTPTokenImport, cls).add_options(parser)
diff --git a/ipatests/test_ipaserver/test_otptoken_import.py b/ipatests/test_ipaserver/test_otptoken_import.py
index b885cefe0..19dfbf74d 100644
--- a/ipatests/test_ipaserver/test_otptoken_import.py
+++ b/ipatests/test_ipaserver/test_otptoken_import.py
@@ -19,16 +19,13 @@
import os
import pytest
-from nss import nss
from ipaserver.install.ipa_otptoken_import import PSKCDocument, ValidationError
basename = os.path.join(os.path.dirname(__file__), "data")
-@pytest.mark.skipif(True, reason="Causes NSS errors. Ticket 5192")
@pytest.mark.tier1
class test_otptoken_import(object):
-
def test_figure3(self):
doc = PSKCDocument(os.path.join(basename, "pskc-figure3.xml"))
assert doc.keyname is None
@@ -63,62 +60,47 @@ class test_otptoken_import(object):
assert False
def test_figure6(self):
- nss.nss_init_nodb()
- try:
- doc = PSKCDocument(os.path.join(basename, "pskc-figure6.xml"))
- assert doc.keyname == 'Pre-shared-key'
- doc.setKey('12345678901234567890123456789012'.decode('hex'))
- assert [(t.id, t.options) for t in doc.getKeyPackages()] == \
- [(u'12345678', {
- 'ipatokenotpkey': u'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ',
- 'ipatokenvendor': u'Manufacturer',
- 'ipatokenserial': u'987654321',
- 'ipatokenhotpcounter': 0,
- 'ipatokenotpdigits': 8,
- 'type': u'hotp'})]
- finally:
- nss.nss_shutdown()
+ doc = PSKCDocument(os.path.join(basename, "pskc-figure6.xml"))
+ assert doc.keyname == 'Pre-shared-key'
+ doc.setKey('12345678901234567890123456789012'.decode('hex'))
+ assert [(t.id, t.options) for t in doc.getKeyPackages()] == \
+ [(u'12345678', {
+ 'ipatokenotpkey': u'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ',
+ 'ipatokenvendor': u'Manufacturer',
+ 'ipatokenserial': u'987654321',
+ 'ipatokenhotpcounter': 0,
+ 'ipatokenotpdigits': 8,
+ 'type': u'hotp'})]
def test_figure7(self):
- nss.nss_init_nodb()
- try:
- doc = PSKCDocument(os.path.join(basename, "pskc-figure7.xml"))
- assert doc.keyname == 'My Password 1'
- doc.setKey('qwerty')
- assert [(t.id, t.options) for t in doc.getKeyPackages()] == \
- [(u'123456', {
- 'ipatokenotpkey': u'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ',
- 'ipatokenvendor': u'TokenVendorAcme',
- 'ipatokenserial': u'987654321',
- 'ipatokenotpdigits': 8,
- 'type': u'hotp'})]
- finally:
- nss.nss_shutdown()
+ doc = PSKCDocument(os.path.join(basename, "pskc-figure7.xml"))
+ assert doc.keyname == 'My Password 1'
+ doc.setKey('qwerty')
+ assert [(t.id, t.options) for t in doc.getKeyPackages()] == \
+ [(u'123456', {
+ 'ipatokenotpkey': u'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ',
+ 'ipatokenvendor': u'TokenVendorAcme',
+ 'ipatokenserial': u'987654321',
+ 'ipatokenotpdigits': 8,
+ 'type': u'hotp'})]
def test_figure8(self):
- nss.nss_init_nodb()
try:
PSKCDocument(os.path.join(basename, "pskc-figure8.xml"))
except NotImplementedError: # X.509 is not supported.
pass
else:
assert False
- finally:
- nss.nss_shutdown()
def test_invalid(self):
- nss.nss_init_nodb()
try:
PSKCDocument(os.path.join(basename, "pskc-invalid.xml"))
except ValueError: # File is invalid.
pass
else:
assert False
- finally:
- nss.nss_shutdown()
def test_mini(self):
- nss.nss_init_nodb()
try:
doc = PSKCDocument(os.path.join(basename, "pskc-mini.xml"))
for t in doc.getKeyPackages():
@@ -127,28 +109,22 @@ class test_otptoken_import(object):
pass
else:
assert False
- finally:
- nss.nss_shutdown()
def test_full(self):
- nss.nss_init_nodb()
- try:
- doc = PSKCDocument(os.path.join(basename, "full.xml"))
- assert [(t.id, t.options) for t in doc.getKeyPackages()] == \
- [(u'KID1', {
- 'ipatokenotpkey': u'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ',
- 'ipatokennotafter': u'20060531000000Z',
- 'ipatokennotbefore': u'20060501000000Z',
- 'ipatokenserial': u'SerialNo-IssueNo',
- 'ipatokentotpclockoffset': 60000,
- 'ipatokenotpalgorithm': u'sha1',
- 'ipatokenvendor': u'iana.dummy',
- 'description': u'FriendlyName',
- 'ipatokentotptimestep': 200,
- 'ipatokenhotpcounter': 0,
- 'ipatokenmodel': u'Model',
- 'ipatokenotpdigits': 8,
- 'type': u'hotp',
- })]
- finally:
- nss.nss_shutdown()
+ doc = PSKCDocument(os.path.join(basename, "full.xml"))
+ assert [(t.id, t.options) for t in doc.getKeyPackages()] == \
+ [(u'KID1', {
+ 'ipatokenotpkey': u'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ',
+ 'ipatokennotafter': u'20060531000000Z',
+ 'ipatokennotbefore': u'20060501000000Z',
+ 'ipatokenserial': u'SerialNo-IssueNo',
+ 'ipatokentotpclockoffset': 60000,
+ 'ipatokenotpalgorithm': u'sha1',
+ 'ipatokenvendor': u'iana.dummy',
+ 'description': u'FriendlyName',
+ 'ipatokentotptimestep': 200,
+ 'ipatokenhotpcounter': 0,
+ 'ipatokenmodel': u'Model',
+ 'ipatokenotpdigits': 8,
+ 'type': u'hotp',
+ })]