On Fri, 2015-09-25 at 18:29 +0200, Martin Babinsky wrote:
> On 09/25/2015 04:53 PM, Nathaniel McCallum wrote:
> > On Mon, 2015-08-31 at 11:08 -0400, Nathaniel McCallum wrote:
> > > https://fedorahosted.org/freeipa/ticket/5192
> > > --
> > > Manage your subscription for the Freeipa-devel mailing list:
> > > https://www.redhat.com/mailman/listinfo/freeipa-devel
> > > Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Cod
> > > e
> >
> > Attached patch rebases the previous patch for master.
> >
> > Nathaniel
> >
> >
> >
> Hi Nathaniel,
>
> pylint is not happy with your patches:
>
> """
> ************* Module ipaserver.install.ipa_otptoken_import
> ipaserver/install/ipa_otptoken_import.py:189:
> [E1120(no-value-for-parameter), PBKDF2KeyDerivation.__init__] No
> value
> for argument 'backend' in constructor call)
> ipaserver/install/ipa_otptoken_import.py:235:
> [E1120(no-value-for-parameter), XMLDecryptor.__call__] No value for
> argument 'backend' in constructor call)
> """
>
> This is probably the reason for 2 of the otptoken_import tests to
> fail
> with TypeError, see http://fpaste.org/271526/31985721/
Fixed.
From 67f95c83b20baa93abe117b3a2d39b2e62785f3c Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Mon, 31 Aug 2015 10:46:19 -0400
Subject: [PATCH] Migrate OTP import script to python-cryptography
https://fedorahosted.org/freeipa/ticket/5192
---
ipaserver/install/ipa_otptoken_import.py | 99 ++++++++--------------
ipatests/test_ipaserver/test_otptoken_import.py | 104 +++++++++---------------
2 files changed, 74 insertions(+), 129 deletions(-)
diff --git a/ipaserver/install/ipa_otptoken_import.py b/ipaserver/install/ipa_otptoken_import.py
index 6377c069686932080e6cf90076612f66745c67a1..a116ce2430416ca348c246c33afd5f055c483da5 100644
--- a/ipaserver/install/ipa_otptoken_import.py
+++ b/ipaserver/install/ipa_otptoken_import.py
@@ -24,16 +24,18 @@ import hashlib
import hmac
import os
import uuid
-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.kdf import pbkdf2
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+from cryptography.hazmat.backends import default_backend
+
from ipapython import admintool
from ipalib import api, errors
from ipaserver.plugins.ldap2 import ldap2
@@ -117,13 +119,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",
@@ -133,7 +135,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):
@@ -168,50 +170,24 @@ 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!")
+ self.kdf = pbkdf2.PBKDF2HMAC(hmod, klen, salt, itrs, default_backend())
+
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
-
- # 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]
+ return self.kdf.derive(masterkey)
def convertKeyDerivation(value):
@@ -228,13 +204,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()
@@ -242,13 +222,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), default_backend())
+ decryptor = cipher.decryptor()
+
+ out = decryptor.update(data)
+ out += decryptor.finalize()
return out
@@ -468,14 +449,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 798f721f5f921da3648e9b285b4f50e18770f448..231c6923f619ed5dee31fd3d44d793b7dedb9736 100644
--- a/ipatests/test_ipaserver/test_otptoken_import.py
+++ b/ipatests/test_ipaserver/test_otptoken_import.py
@@ -21,19 +21,12 @@ import os
import sys
import nose
import pytest
-from nss import nss
-from ipalib.x509 import initialize_nss_database
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")
class test_otptoken_import(object):
-
- def teardown(self):
- initialize_nss_database()
-
def test_figure3(self):
doc = PSKCDocument(os.path.join(basename, "pskc-figure3.xml"))
assert doc.keyname is None
@@ -68,62 +61,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:
doc = 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:
doc = 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"))
[(t.id, t.options) for t in doc.getKeyPackages()]
@@ -131,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',
+ })]
--
2.5.0
--
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code