summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--freeipa.spec.in2
-rw-r--r--install/tools/Makefile.am1
-rwxr-xr-xinstall/tools/ipa-otptoken-import25
-rw-r--r--install/tools/man/Makefile.am1
-rw-r--r--install/tools/man/ipa-otptoken-import.136
-rw-r--r--ipaserver/install/ipa_otptoken_import.py530
-rw-r--r--ipatests/test_ipaserver/data/full.xml48
-rw-r--r--ipatests/test_ipaserver/data/pskc-figure3.xml32
-rw-r--r--ipatests/test_ipaserver/data/pskc-figure4.xml31
-rw-r--r--ipatests/test_ipaserver/data/pskc-figure5.xml57
-rw-r--r--ipatests/test_ipaserver/data/pskc-figure6.xml47
-rw-r--r--ipatests/test_ipaserver/data/pskc-figure7.xml68
-rw-r--r--ipatests/test_ipaserver/data/pskc-figure8.xml53
-rw-r--r--ipatests/test_ipaserver/data/pskc-invalid.xml3
-rw-r--r--ipatests/test_ipaserver/data/pskc-mini.xml4
-rw-r--r--ipatests/test_ipaserver/test_otptoken_import.py151
16 files changed, 1089 insertions, 0 deletions
diff --git a/freeipa.spec.in b/freeipa.spec.in
index e19fd2a19..4631a5936 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -305,6 +305,7 @@ Requires: python-netaddr
Requires: libipa_hbac-python
Requires: python-qrcode
Requires: python-pyasn1
+Requires: python-dateutil
Obsoletes: ipa-python >= 1.0
@@ -638,6 +639,7 @@ fi
%{_sbindir}/ipa-csreplica-manage
%{_sbindir}/ipa-server-certinstall
%{_sbindir}/ipa-ldap-updater
+%{_sbindir}/ipa-otptoken-import
%{_sbindir}/ipa-compat-manage
%{_sbindir}/ipa-nis-manage
%{_sbindir}/ipa-managed-entries
diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
index 2cf66c6df..485be91b7 100644
--- a/install/tools/Makefile.am
+++ b/install/tools/Makefile.am
@@ -20,6 +20,7 @@ sbin_SCRIPTS = \
ipa-nis-manage \
ipa-managed-entries \
ipa-ldap-updater \
+ ipa-otptoken-import \
ipa-upgradeconfig \
ipa-backup \
ipa-restore \
diff --git a/install/tools/ipa-otptoken-import b/install/tools/ipa-otptoken-import
new file mode 100755
index 000000000..090116dab
--- /dev/null
+++ b/install/tools/ipa-otptoken-import
@@ -0,0 +1,25 @@
+#! /usr/bin/python2 -E
+# Authors: Nathaniel McCallum <npmccallum@redhat.com>
+#
+# Copyright (C) 2014 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from ipaserver.install.ipa_otptoken_import import OTPTokenImport
+import nss.nss as nss
+
+OTPTokenImport.run_cli()
+
diff --git a/install/tools/man/Makefile.am b/install/tools/man/Makefile.am
index 33e8a9e4b..b3f39b942 100644
--- a/install/tools/man/Makefile.am
+++ b/install/tools/man/Makefile.am
@@ -22,6 +22,7 @@ man1_MANS = \
ipa-backup.1 \
ipa-restore.1 \
ipa-advise.1 \
+ ipa-otptoken-import.1 \
$(NULL)
man8_MANS = \
diff --git a/install/tools/man/ipa-otptoken-import.1 b/install/tools/man/ipa-otptoken-import.1
new file mode 100644
index 000000000..920a08ca2
--- /dev/null
+++ b/install/tools/man/ipa-otptoken-import.1
@@ -0,0 +1,36 @@
+.\" A man page for ipa-otptoken-import
+.\" Copyright (C) 2014 Red Hat, Inc.
+.\"
+.\" This program 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.
+.\"
+.\" This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+.\"
+.\" Author: Nathaniel McCallum <npmccallum@redhat.com>
+.\"
+.TH "ipa-otptoken-import" "1" "Jun 12 2014" "FreeIPA" "FreeIPA Manual Pages"
+.SH "NAME"
+ipa\-otptoken\-import \- Imports OTP tokens from RFC 6030 XML file
+.SH "SYNOPSIS"
+ipa\-otptoken\-import [options] <infile> <outfile>
+.SH "DESCRIPTION"
+Running the command will attempt to import all tokens specified in \fBinfile\fR. If the command is unable to import a token, the reason for the failure will be printed to standard error and all failed tokens will be written to the \fBoutfile\fR for further inspection.
+
+If the \fBinfile\fR contains encrypted token data, then the \fIkeyfile\fR (\fB-k\fR) option MUST be specified.
+
+.SH "OPTIONS"
+.TP
+\fB\-k\fR \fIkeyfile\fR
+File containing the key used to decrypt the token data.
+.SH "EXIT STATUS"
+0 if the command was successful
+
+1 if an error occurred
diff --git a/ipaserver/install/ipa_otptoken_import.py b/ipaserver/install/ipa_otptoken_import.py
new file mode 100644
index 000000000..31a690201
--- /dev/null
+++ b/ipaserver/install/ipa_otptoken_import.py
@@ -0,0 +1,530 @@
+# Authors: Nathaniel McCallum <npmccallum@redhat.com>
+#
+# Copyright (C) 2014 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import abc
+import base64
+import datetime
+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 krbV
+
+from ipapython import admintool
+from ipalib import api, errors
+from ipaserver.plugins.ldap2 import ldap2
+
+
+class ValidationError(Exception):
+ pass
+
+
+def fetchAll(element, xpath, conv=lambda x: x):
+ return map(conv, element.xpath(xpath, namespaces={
+ "pskc": "urn:ietf:params:xml:ns:keyprov:pskc",
+ "xenc11": "http://www.w3.org/2009/xmlenc11#",
+ "xenc": "http://www.w3.org/2001/04/xmlenc#",
+ "ds": "http://www.w3.org/2000/09/xmldsig#",
+ }))
+
+
+def fetch(element, xpath, conv=lambda x: x, default=None):
+ result = fetchAll(element, xpath, conv)
+ return result[0] if result else default
+
+
+def convertDate(value):
+ "Converts an ISO 8601 string into a UTC datetime object."
+
+ dt = dateutil.parser.parse(value)
+
+ if dt.tzinfo is None:
+ dt = datetime.datetime(*dt.timetuple()[0:6],
+ tzinfo=dateutil.tz.tzlocal())
+
+ return dt.astimezone(dateutil.tz.tzutc())
+
+
+def convertTokenType(value):
+ "Converts token algorithm URI to token type string."
+
+ return {
+ "urn:ietf:params:xml:ns:keyprov:pskc:hotp": u"hotp",
+ "urn:ietf:params:xml:ns:keyprov:pskc#hotp": u"hotp",
+ "urn:ietf:params:xml:ns:keyprov:pskc:totp": u"totp",
+ "urn:ietf:params:xml:ns:keyprov:pskc#totp": u"totp",
+ }.get(value.lower(), None)
+
+
+def convertHashName(value):
+ "Converts hash names to their canonical names."
+
+ return {
+ "sha1": u"sha1",
+ "sha224": u"sha224",
+ "sha256": u"sha256",
+ "sha384": u"sha384",
+ "sha512": u"sha512",
+ "sha-1": u"sha1",
+ "sha-224": u"sha224",
+ "sha-256": u"sha256",
+ "sha-384": u"sha384",
+ "sha-512": u"sha512",
+ }.get(value.lower(), u"sha1")
+
+
+def convertHMACType(value):
+ "Converts HMAC URI to hashlib object."
+
+ return {
+ "http://www.w3.org/2000/09/xmldsig#hmac-sha1": hashlib.sha1,
+ "http://www.w3.org/2001/04/xmldsig-more#hmac-sha224": hashlib.sha224,
+ "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256": hashlib.sha256,
+ "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384": hashlib.sha384,
+ "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512": hashlib.sha512,
+ }.get(value.lower(), hashlib.sha1)
+
+
+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),
+
+ # TODO: add support for these formats.
+ # "http://www.w3.org/2001/04/xmlenc#kw-aes128": "kw-aes128",
+ # "http://www.w3.org/2001/04/xmlenc#kw-aes192": "kw-aes192",
+ # "http://www.w3.org/2001/04/xmlenc#kw-aes256": "kw-aes256",
+ # "http://www.w3.org/2001/04/xmlenc#kw-tripledes": "kw-tripledes",
+ # "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))
+
+
+def convertEncrypted(value, decryptor=None, pconv=base64.b64decode, econv=lambda x: x):
+ "Converts a value element, decrypting if necessary. See RFC 6030."
+
+ v = fetch(value, "./pskc:PlainValue/text()", pconv)
+ if v is not None:
+ return v
+
+ mac = fetch(value, "./pskc:ValueMAC/text()", base64.b64decode)
+ ev = fetch(value, "./pskc:EncryptedValue")
+ if ev is not None and decryptor is not None:
+ return econv(decryptor(ev, mac))
+
+ return None
+
+
+class XMLKeyDerivation(object):
+ "Interface for XML Encryption 1.1 key derivation."
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __init__(self, enckey):
+ "Sets up key derivation parameters from the parent XML entity."
+
+ @abc.abstractmethod
+ def derive(self, masterkey):
+ "Derives a key from the master key."
+
+
+class PBKDF2KeyDerivation(XMLKeyDerivation):
+ def __init__(self, enckey):
+ params = fetch(enckey, "./xenc11:DerivedKey/xenc11:KeyDerivationMethod/xenc11:PBKDF2-params")
+ 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)
+
+ if self.salt is None:
+ raise ValueError("XML file is missing PBKDF2 salt!")
+
+ if self.iter is None:
+ raise ValueError("XML file is missing PBKDF2 iteration count!")
+
+ if self.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 xrange(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 xrange(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 convertKeyDerivation(value):
+ "Converts key derivation URI to a BaseKeyDerivation class."
+
+ return {
+ "http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2": PBKDF2KeyDerivation,
+ }.get(value.lower(), None)
+
+
+class XMLDecryptor(object):
+ """This decrypts values from XML as specified in:
+ * http://www.w3.org/TR/xmlenc-core/
+ * RFC 6931"""
+
+ def __init__(self, key, hmac=None):
+ self.__key = nss.SecItem(key)
+ self.__hmac = hmac
+
+ def __call__(self, element, mac=None):
+ (mech, ivlen) = fetch(element, "./xenc:EncryptionMethod/@Algorithm", convertAlgorithm)
+ data = fetch(element, "./xenc:CipherData/xenc:CipherValue/text()", base64.b64decode)
+
+ # If a MAC is present, perform validation.
+ if mac:
+ tmp = self.__hmac.copy()
+ tmp.update(data)
+ 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()
+ return out
+
+
+class PSKCKeyPackage(object):
+ _XML = {
+ 'pskc:DeviceInfo': {
+ 'pskc:IssueNo/text()': ('issueno', unicode),
+ 'pskc:ExpiryDate/text()': ('notafter.hw', convertDate),
+ 'pskc:Manufacturer/text()': ('vendor', unicode),
+ 'pskc:Model/text()': ('model', unicode),
+ 'pskc:SerialNo/text()': ('serial', unicode),
+ 'pskc:StartDate/text()': ('notbefore.hw', convertDate),
+ 'pskc:UserId/text()': ('owner', unicode),
+ },
+
+ 'pskc:Key': {
+ '@Algorithm': ('type', convertTokenType),
+ '@Id': ('id', unicode),
+ 'pskc:FriendlyName/text()': ('description', unicode),
+ 'pskc:Issuer/text()': ('issuer', unicode),
+ 'pskc:KeyReference/text()': ('keyref', unicode),
+
+ 'pskc:AlgorithmParameters': {
+ 'pskc:Suite/text()': ('algorithm', convertHashName),
+ 'pskc:ResponseFormat/@CheckDigit': ('checkdigit', unicode),
+ 'pskc:ResponseFormat/@Encoding': ('encoding', unicode),
+ 'pskc:ResponseFormat/@Length': ('digits', int),
+ },
+
+ 'pskc:Data': {
+ 'pskc:Counter': ('counter', lambda v, d: convertEncrypted(v, d, long, long)),
+ 'pskc:Secret': ('key', convertEncrypted),
+ 'pskc:Time': ('time', lambda v, d: convertEncrypted(v, d, int, int)),
+ 'pskc:TimeDrift': ('offset', lambda v, d: convertEncrypted(v, d, int, int)),
+ 'pskc:TimeInterval': ('interval', lambda v, d: convertEncrypted(v, d, int, int)),
+ },
+
+ 'pskc:Policy': {
+ 'pskc:ExpiryDate/text()': ('notafter.sw', convertDate),
+ 'pskc:KeyUsage/text()': ('keyusage', unicode),
+ 'pskc:NumberOfTransactions': ('maxtransact', lambda v: v),
+ 'pskc:PINPolicy': ('pinpolicy', lambda v: v),
+ 'pskc:StartDate/text()': ('notbefore.sw', convertDate),
+ },
+ },
+ }
+
+ _MAP = (
+ ('type', 'type', lambda v, o: v.strip()),
+ ('description', 'description', lambda v, o: v.strip()),
+ ('vendor', 'ipatokenvendor', lambda v, o: v.strip()),
+ ('model', 'ipatokenmodel', lambda v, o: v.strip()),
+ ('serial', 'ipatokenserial', lambda v, o: v.strip()),
+ ('issueno', 'ipatokenserial', lambda v, o: o.get('ipatokenserial', '') + '-' + v.strip()),
+ ('key', 'ipatokenotpkey', lambda v, o: unicode(base64.b32encode(v))),
+ ('digits', 'ipatokenotpdigits', lambda v, o: v),
+ ('algorithm', 'ipatokenotpalgorithm', lambda v, o: v),
+ ('counter', 'ipatokenhotpcounter', lambda v, o: v),
+ ('interval', 'ipatokentotptimestep', lambda v, o: v),
+ ('offset', 'ipatokentotpclockoffset', lambda v, o: o.get('ipatokentotptimestep', 30) * v),
+ )
+
+ def __init__(self, element, decryptor):
+ self.__element = element
+ self.__decryptor = decryptor
+ self.__id = None
+ self.__options = None
+
+ @property
+ def id(self):
+ if self.__id is None:
+ self.__process()
+
+ return self.__id
+
+ @property
+ def options(self):
+ if self.__options is None:
+ self.__process()
+
+ return self.__options
+
+ def remove(self):
+ self.__element.getparent().remove(self.__element)
+
+ def __process(self):
+ # Parse and validate.
+ data = self.__parse(self.__decryptor, self.__element, ".", self._XML)
+ self.__validate(data)
+
+ # Copy values into output.
+ options = {}
+ for (dk, ok, f) in self._MAP:
+ if dk in data:
+ options[ok] = f(data[dk], options)
+
+ # Copy validity dates.
+ self.__dates(options, data, 'notbefore', max)
+ self.__dates(options, data, 'notafter', min)
+
+ # Save attributes.
+ self.__options = options
+ self.__id = data.get('id', uuid.uuid4())
+
+ def __parse(self, decryptor, element, prefix, table):
+ "Recursively parses the xml from a table."
+
+ data = {}
+ for k, v in table.items():
+ path = prefix + "/" + k
+
+ if isinstance(v, dict):
+ data.update(self.__parse(decryptor, element, path, v))
+ continue
+
+ result = fetch(element, path)
+ if result is not None:
+ if getattr(getattr(v[1], "func_code", None), "co_argcount", 0) > 1:
+ data[v[0]] = v[1](result, decryptor)
+ else:
+ data[v[0]] = v[1](result)
+
+ return data
+
+ def __validate(self, data):
+ "Validates the parsed data."
+
+ if 'type' not in data or data['type'] not in ('totp', 'hotp'):
+ raise ValidationError("Unsupported token type!")
+
+ if 'key' not in data:
+ if 'keyref' in data:
+ raise ValidationError("Referenced keys are not supported!")
+ raise ValidationError("Key not found in token!")
+
+ if data.get('checkdigit', 'FALSE').upper() != 'FALSE':
+ raise ValidationError("CheckDigit not supported!")
+
+ if data.get('maxtransact', None) is not None:
+ raise ValidationError('NumberOfTransactions policy not supported!')
+
+ if data.get('pinpolicy', None) is not None:
+ raise ValidationError('PINPolicy policy not supported!')
+
+ if data.get('time', 0) != 0:
+ raise ValidationError('Specified time is not supported!')
+
+ encoding = data.get('encoding', 'DECIMAL').upper()
+ if encoding != 'DECIMAL':
+ raise ValidationError('Unsupported encoding: %s!' % encoding)
+
+ usage = data.get('keyusage', 'OTP')
+ if usage != 'OTP':
+ raise ValidationError('Unsupported key usage: %s' % usage)
+
+ def __dates(self, out, data, key, reducer):
+ dates = (data.get(key + '.sw', None), data.get(key + '.hw', None))
+ dates = filter(lambda x: x is not None, dates)
+ if dates:
+ out['ipatoken' + key] = unicode(reducer(dates).strftime("%Y%m%d%H%M%SZ"))
+
+
+class PSKCDocument(object):
+ @property
+ def keyname(self):
+ return self.__keyname
+
+ def __init__(self, filename):
+ self.__keyname = None
+ self.__decryptor = None
+ self.__doc = etree.parse(filename)
+ self.__mkey = fetch(self.__doc, "./pskc:MACMethod/pskc:MACKey")
+ self.__algo = fetch(self.__doc, "./pskc:MACMethod/@Algorithm", convertHMACType)
+
+ self.__keypackages = fetchAll(self.__doc, "./pskc:KeyPackage")
+ if not self.__keypackages:
+ raise ValueError("PSKC file is invalid!")
+
+ self.__enckey = fetch(self.__doc, "./pskc:EncryptionKey")
+ if self.__enckey is not None:
+ # Check for x509 key.
+ x509key = fetch(self.__enckey, "./ds:X509Data")
+ if x509key is not None:
+ raise NotImplementedError("X.509 keys are not currently supported!")
+
+ # Get the keyname.
+ self.__keyname = fetch(self.__enckey, "./ds:KeyName/text()")
+ if self.__keyname is None:
+ self.__keyname = fetch(self.__enckey,
+ "./xenc11:DerivedKey/xenc11:MasterKeyName/text()")
+
+ def setKey(self, key):
+ # Derive the enckey if required.
+ kd = fetch(self.__enckey,
+ "./xenc11:DerivedKey/xenc11:KeyDerivationMethod/@Algorithm",
+ convertKeyDerivation)
+ if kd is not None:
+ key = kd(self.__enckey).derive(key)
+
+ # Load the decryptor.
+ self.__decryptor = XMLDecryptor(key)
+ if self.__mkey is not None and self.__algo is not None:
+ tmp = hmac.HMAC(self.__decryptor(self.__mkey), digestmod=self.__algo)
+ self.__decryptor = XMLDecryptor(key, tmp)
+
+ def getKeyPackages(self):
+ for kp in self.__keypackages:
+ yield PSKCKeyPackage(kp, self.__decryptor)
+
+ def save(self, dest):
+ self.__doc.write(dest)
+
+
+class OTPTokenImport(admintool.AdminTool):
+ command_name = 'ipa-otptoken-import'
+ description = "Import OTP tokens."
+ 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)
+
+ parser.add_option("-k", "--keyfile", dest="keyfile",
+ help="File containing the key used to decrypt token secrets")
+
+ def validate_options(self):
+ super(OTPTokenImport, self).validate_options()
+
+ # Parse the file.
+ if len(self.args) < 1:
+ raise admintool.ScriptError("Import file required!")
+ self.doc = PSKCDocument(self.args[0])
+
+ # Get the output file.
+ if len(self.args) < 2:
+ raise admintool.ScriptError("Output file required!")
+ self.output = self.args[1]
+ if os.path.exists(self.output):
+ raise admintool.ScriptError("Output file already exists!")
+
+ # Verify a key is provided if one is needed.
+ if self.doc.keyname is not None:
+ if self.safe_options.keyfile is None:
+ raise admintool.ScriptError("Encryption key required: %s!" % self.doc.keyname)
+
+ # Load the keyfile.
+ with open(self.safe_options.keyfile) as f:
+ self.doc.setKey(f.read())
+
+ def run(self):
+ api.bootstrap(in_server=True)
+ api.finalize()
+
+ conn = ldap2()
+ try:
+ ccache = krbV.default_context().default_ccache()
+ conn.connect(ccache=ccache)
+ except (krbV.Krb5Error, errors.ACIError):
+ raise admintool.ScriptError("Unable to connect to LDAP! Did you kinit?")
+
+ try:
+ # Parse tokens
+ for keypkg in self.doc.getKeyPackages():
+ try:
+ api.Command.otptoken_add(keypkg.id, **keypkg.options)
+ except Exception as e:
+ self.log.warn("Error adding token: %s", e)
+ else:
+ self.log.info("Added token: %s", keypkg.id)
+ keypkg.remove()
+ finally:
+ conn.disconnect()
+
+ # Write out the XML file without the tokens that succeeded.
+ self.doc.save(self.output)
diff --git a/ipatests/test_ipaserver/data/full.xml b/ipatests/test_ipaserver/data/full.xml
new file mode 100644
index 000000000..0281b2881
--- /dev/null
+++ b/ipatests/test_ipaserver/data/full.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<KeyContainer xmlns="urn:ietf:params:xml:ns:keyprov:pskc" Version="1.0" Id="KCID">
+ <KeyPackage>
+ <DeviceInfo>
+ <Manufacturer>iana.dummy</Manufacturer>
+ <SerialNo>SerialNo</SerialNo>
+ <Model>Model</Model>
+ <IssueNo>IssueNo</IssueNo>
+ <DeviceBinding>DeviceBinding</DeviceBinding>
+ <StartDate>2006-05-01T00:00:00Z</StartDate>
+ <ExpiryDate>2012-05-01T00:00:00Z</ExpiryDate>
+ <UserId>DeviceUserId</UserId>
+ </DeviceInfo>
+ <CryptoModuleInfo>
+ <Id>CMID</Id>
+ </CryptoModuleInfo>
+ <Key Id="KID1" Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp">
+ <Issuer>Issuer</Issuer>
+ <AlgorithmParameters>
+ <Suite>Suite</Suite>
+ <ChallengeFormat Encoding="DECIMAL" Min="42" Max="4711" CheckDigits="true"/>
+ <ResponseFormat Encoding="DECIMAL" Length="8" CheckDigits="true"/>
+ </AlgorithmParameters>
+ <KeyProfileId>KeyProfileId</KeyProfileId>
+ <KeyReference>KeyReference</KeyReference>
+ <FriendlyName>FriendlyName</FriendlyName>
+ <Data>
+ <Secret>
+ <PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=</PlainValue>
+ </Secret>
+ <Counter>
+ <PlainValue>0</PlainValue>
+ </Counter>
+ <TimeInterval>
+ <PlainValue>200</PlainValue>
+ </TimeInterval>
+ <TimeDrift>
+ <PlainValue>300</PlainValue>
+ </TimeDrift>
+ </Data>
+ <UserId>KeyUserId</UserId>
+ <Policy>
+ <StartDate>2006-05-01T00:00:00Z</StartDate>
+ <ExpiryDate>2006-05-31T00:00:00Z</ExpiryDate>
+ </Policy>
+ </Key>
+ </KeyPackage>
+</KeyContainer>
diff --git a/ipatests/test_ipaserver/data/pskc-figure3.xml b/ipatests/test_ipaserver/data/pskc-figure3.xml
new file mode 100644
index 000000000..b02ac7945
--- /dev/null
+++ b/ipatests/test_ipaserver/data/pskc-figure3.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<KeyContainer Version="1.0"
+ Id="exampleID1"
+ xmlns="urn:ietf:params:xml:ns:keyprov:pskc">
+ <KeyPackage>
+ <DeviceInfo>
+ <Manufacturer>Manufacturer</Manufacturer>
+ <SerialNo>987654321</SerialNo>
+ <UserId>DC=example-bank,DC=net</UserId>
+ </DeviceInfo>
+ <CryptoModuleInfo>
+ <Id>CM_ID_001</Id>
+ </CryptoModuleInfo>
+ <Key Id="12345678"
+ Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp">
+ <Issuer>Issuer</Issuer>
+ <AlgorithmParameters>
+ <ResponseFormat Length="8" Encoding="DECIMAL"/>
+ </AlgorithmParameters>
+ <Data>
+ <Secret>
+ <PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
+ </PlainValue>
+ </Secret>
+ <Counter>
+ <PlainValue>0</PlainValue>
+ </Counter>
+ </Data>
+ <UserId>UID=jsmith,DC=example-bank,DC=net</UserId>
+ </Key>
+ </KeyPackage>
+</KeyContainer>
diff --git a/ipatests/test_ipaserver/data/pskc-figure4.xml b/ipatests/test_ipaserver/data/pskc-figure4.xml
new file mode 100644
index 000000000..186e02901
--- /dev/null
+++ b/ipatests/test_ipaserver/data/pskc-figure4.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<KeyContainer Version="1.0" Id="exampleID1"
+ xmlns="urn:ietf:params:xml:ns:keyprov:pskc">
+ <KeyPackage>
+ <DeviceInfo>
+ <Manufacturer>Manufacturer</Manufacturer>
+ <SerialNo>987654321</SerialNo>
+ </DeviceInfo>
+ <CryptoModuleInfo>
+ <Id>CM_ID_001</Id>
+ </CryptoModuleInfo>
+ <Key Id="12345678"
+ Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp">
+ <Issuer>Issuer</Issuer>
+ <AlgorithmParameters>
+ <ResponseFormat Length="8" Encoding="DECIMAL"/>
+ </AlgorithmParameters>
+ <KeyProfileId>keyProfile1</KeyProfileId>
+ <KeyReference>MasterKeyLabel
+ </KeyReference>
+ <Data>
+ <Counter>
+ <PlainValue>0</PlainValue>
+ </Counter>
+ </Data>
+ <Policy>
+ <KeyUsage>OTP</KeyUsage>
+ </Policy>
+ </Key>
+ </KeyPackage>
+</KeyContainer>
diff --git a/ipatests/test_ipaserver/data/pskc-figure5.xml b/ipatests/test_ipaserver/data/pskc-figure5.xml
new file mode 100644
index 000000000..16ab9bb3c
--- /dev/null
+++ b/ipatests/test_ipaserver/data/pskc-figure5.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<KeyContainer
+ Version="1.0" Id="exampleID1"
+ xmlns="urn:ietf:params:xml:ns:keyprov:pskc">
+ <KeyPackage>
+ <DeviceInfo>
+ <Manufacturer>Manufacturer</Manufacturer>
+ <SerialNo>987654321</SerialNo>
+ </DeviceInfo>
+ <CryptoModuleInfo>
+ <Id>CM_ID_001</Id>
+ </CryptoModuleInfo>
+ <Key Id="12345678"
+ Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp">
+ <Issuer>Issuer</Issuer>
+ <AlgorithmParameters>
+ <ResponseFormat Length="8" Encoding="DECIMAL"/>
+ </AlgorithmParameters>
+ <Data>
+ <Secret>
+ <PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
+ </PlainValue>
+ </Secret>
+ <Counter>
+ <PlainValue>0</PlainValue>
+ </Counter>
+ </Data>
+ <Policy>
+ <PINPolicy MinLength="4" MaxLength="4"
+ PINKeyId="123456781" PINEncoding="DECIMAL"
+ PINUsageMode="Local"/>
+ <KeyUsage>OTP</KeyUsage>
+ </Policy>
+ </Key>
+ </KeyPackage>
+ <KeyPackage>
+ <DeviceInfo>
+ <Manufacturer>Manufacturer</Manufacturer>
+ <SerialNo>987654321</SerialNo>
+ </DeviceInfo>
+ <CryptoModuleInfo>
+ <Id>CM_ID_001</Id>
+ </CryptoModuleInfo>
+ <Key Id="123456781"
+ Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:pin">
+ <Issuer>Issuer</Issuer>
+ <AlgorithmParameters>
+ <ResponseFormat Length="4" Encoding="DECIMAL"/>
+ </AlgorithmParameters>
+ <Data>
+ <Secret>
+ <PlainValue>MTIzNA==</PlainValue>
+ </Secret>
+ </Data>
+ </Key>
+ </KeyPackage>
+</KeyContainer>
diff --git a/ipatests/test_ipaserver/data/pskc-figure6.xml b/ipatests/test_ipaserver/data/pskc-figure6.xml
new file mode 100644
index 000000000..0f4cd334f
--- /dev/null
+++ b/ipatests/test_ipaserver/data/pskc-figure6.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<KeyContainer Version="1.0"
+ xmlns="urn:ietf:params:xml:ns:keyprov:pskc"
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
+ <EncryptionKey>
+ <ds:KeyName>Pre-shared-key</ds:KeyName>
+ </EncryptionKey>
+ <MACMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1">
+ <MACKey>
+ <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+ <xenc:CipherData>
+ <xenc:CipherValue>ESIzRFVmd4iZABEiM0RVZgKn6WjLaTC1sbeBMSvIhRejN9vJa2BOlSaMrR7I5wSX</xenc:CipherValue>
+ </xenc:CipherData>
+ </MACKey>
+ </MACMethod>
+ <KeyPackage>
+ <DeviceInfo>
+ <Manufacturer>Manufacturer</Manufacturer>
+ <SerialNo>987654321</SerialNo>
+ </DeviceInfo>
+ <CryptoModuleInfo>
+ <Id>CM_ID_001</Id>
+ </CryptoModuleInfo>
+ <Key Id="12345678"
+ Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp">
+ <Issuer>Issuer</Issuer>
+ <AlgorithmParameters>
+ <ResponseFormat Length="8" Encoding="DECIMAL"/>
+ </AlgorithmParameters>
+ <Data>
+ <Secret>
+ <EncryptedValue>
+ <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+ <xenc:CipherData>
+ <xenc:CipherValue>AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGv</xenc:CipherValue>
+ </xenc:CipherData>
+ </EncryptedValue>
+ <ValueMAC>Su+NvtQfmvfJzF6bmQiJqoLRExc=</ValueMAC>
+ </Secret>
+ <Counter>
+ <PlainValue>0</PlainValue>
+ </Counter>
+ </Data>
+ </Key>
+ </KeyPackage>
+</KeyContainer>
diff --git a/ipatests/test_ipaserver/data/pskc-figure7.xml b/ipatests/test_ipaserver/data/pskc-figure7.xml
new file mode 100644
index 000000000..1fb04fc31
--- /dev/null
+++ b/ipatests/test_ipaserver/data/pskc-figure7.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer
+ xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc"
+ xmlns:xenc11="http://www.w3.org/2009/xmlenc11#"
+ xmlns:pkcs5="http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#"
+ xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Version="1.0">
+ <pskc:EncryptionKey>
+ <xenc11:DerivedKey>
+ <xenc11:KeyDerivationMethod
+ Algorithm="http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2">
+ <xenc11:PBKDF2-params>
+ <xenc11:Salt>
+ <xenc11:Specified>Ej7/PEpyEpw=</xenc11:Specified>
+ </xenc11:Salt>
+ <xenc11:IterationCount>1000</xenc11:IterationCount>
+ <xenc11:KeyLength>16</xenc11:KeyLength>
+ <xenc11:PRF/>
+ </xenc11:PBKDF2-params>
+ </xenc11:KeyDerivationMethod>
+ <xenc:ReferenceList>
+ <xenc:DataReference URI="#ED"/>
+ </xenc:ReferenceList>
+ <xenc11:MasterKeyName>My Password 1</xenc11:MasterKeyName>
+ </xenc11:DerivedKey>
+ </pskc:EncryptionKey>
+ <pskc:MACMethod
+ Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1">
+ <pskc:MACKey>
+ <xenc:EncryptionMethod
+ Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+ <xenc:CipherData>
+ <xenc:CipherValue>
+ 2GTTnLwM3I4e5IO5FkufoOEiOhNj91fhKRQBtBJYluUDsPOLTfUvoU2dStyOwYZx
+ </xenc:CipherValue>
+ </xenc:CipherData>
+ </pskc:MACKey>
+ </pskc:MACMethod>
+ <pskc:KeyPackage>
+ <pskc:DeviceInfo>
+ <pskc:Manufacturer>TokenVendorAcme</pskc:Manufacturer>
+ <pskc:SerialNo>987654321</pskc:SerialNo>
+ </pskc:DeviceInfo>
+ <pskc:CryptoModuleInfo>
+ <pskc:Id>CM_ID_001</pskc:Id>
+ </pskc:CryptoModuleInfo>
+ <pskc:Key Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp" Id="123456">
+ <pskc:Issuer>Example-Issuer</pskc:Issuer>
+ <pskc:AlgorithmParameters>
+ <pskc:ResponseFormat Length="8" Encoding="DECIMAL"/>
+ </pskc:AlgorithmParameters>
+ <pskc:Data>
+ <pskc:Secret>
+ <pskc:EncryptedValue Id="ED">
+ <xenc:EncryptionMethod
+ Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+ <xenc:CipherData>
+ <xenc:CipherValue>
+ oTvo+S22nsmS2Z/RtcoF8Hfh+jzMe0RkiafpoDpnoZTjPYZu6V+A4aEn032yCr4f
+ </xenc:CipherValue>
+ </xenc:CipherData>
+ </pskc:EncryptedValue>
+ <pskc:ValueMAC>LP6xMvjtypbfT9PdkJhBZ+D6O4w=
+ </pskc:ValueMAC>
+ </pskc:Secret>
+ </pskc:Data>
+ </pskc:Key>
+ </pskc:KeyPackage>
+</pskc:KeyContainer>
diff --git a/ipatests/test_ipaserver/data/pskc-figure8.xml b/ipatests/test_ipaserver/data/pskc-figure8.xml
new file mode 100644
index 000000000..c9f63cf02
--- /dev/null
+++ b/ipatests/test_ipaserver/data/pskc-figure8.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<KeyContainer
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ xmlns="urn:ietf:params:xml:ns:keyprov:pskc"
+ xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
+ Id="KC0001"
+ Version="1.0">
+ <EncryptionKey>
+ <ds:X509Data>
+ <ds:X509Certificate>MIIB5zCCAVCgAwIBAgIESZp/vDANBgkqhkiG9w0BAQUFADA4M
+ Q0wCwYDVQQKEwRJRVRGMRMwEQYDVQQLEwpLZXlQcm92IFdHMRIwEAYDVQQDEwlQU0tDIF
+ Rlc3QwHhcNMDkwMjE3MDkxMzMyWhcNMTEwMjE3MDkxMzMyWjA4MQ0wCwYDVQQKEwRJRVR
+ GMRMwEQYDVQQLEwpLZXlQcm92IFdHMRIwEAYDVQQDEwlQU0tDIFRlc3QwgZ8wDQYJKoZI
+ hvcNAQEBBQADgY0AMIGJAoGBALCWLDa2ItYJ6su80hd1gL4cggQYdyyKK17btt/aS6Q/e
+ DsKjsPyFIODsxeKVV/uA3wLT4jQJM5euKJXkDajzGGOy92+ypfzTX4zDJMkh61SZwlHNJ
+ xBKilAM5aW7C+BQ0RvCxvdYtzx2LTdB+X/KMEBA7uIYxLfXH2Mnub3WIh1AgMBAAEwDQY
+ JKoZIhvcNAQEFBQADgYEAe875m84sYUJ8qPeZ+NG7REgTvlHTmoCdoByU0LBBLotUKuqf
+ rnRuXJRMeZXaaEGmzY1kLonVjQGzjAkU4dJ+RPmiDlYuHLZS41Pg6VMwY+03lhk6I5A/w
+ 4rnqdkmwZX/NgXg06alnc2pBsXWhL4O7nk0S2ZrLMsQZ6HcsXgdmHo=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </EncryptionKey>
+ <KeyPackage>
+ <DeviceInfo>
+ <Manufacturer>TokenVendorAcme</Manufacturer>
+ <SerialNo>987654321</SerialNo>
+ </DeviceInfo>
+ <Key Id="MBK000000001"
+ Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp">
+ <Issuer>Example-Issuer</Issuer>
+ <AlgorithmParameters>
+ <ResponseFormat Length="6" Encoding="DECIMAL"/>
+ </AlgorithmParameters>
+ <Data>
+ <Secret>
+ <EncryptedValue>
+ <xenc:EncryptionMethod
+ Algorithm="http://www.w3.org/2001/04/xmlenc#rsa_1_5"/>
+ <xenc:CipherData>
+ <xenc:CipherValue>hJ+fvpoMPMO9BYpK2rdyQYGIxiATYHTHC7e/sPLKYo5/r1v+4
+ xTYG3gJolCWuVMydJ7Ta0GaiBPHcWa8ctCVYmHKfSz5fdeV5nqbZApe6dofTqhRwZK6
+ Yx4ufevi91cjN2vBpSxYafvN3c3+xIgk0EnTV4iVPRCR0rBwyfFrPc4=
+ </xenc:CipherValue>
+ </xenc:CipherData>
+ </EncryptedValue>
+ </Secret>
+ <Counter>
+ <PlainValue>0</PlainValue>
+ </Counter>
+ </Data>
+ </Key>
+ </KeyPackage>
+</KeyContainer>
diff --git a/ipatests/test_ipaserver/data/pskc-invalid.xml b/ipatests/test_ipaserver/data/pskc-invalid.xml
new file mode 100644
index 000000000..688e3479d
--- /dev/null
+++ b/ipatests/test_ipaserver/data/pskc-invalid.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<SomethingElse>
+</SomethingElse>
diff --git a/ipatests/test_ipaserver/data/pskc-mini.xml b/ipatests/test_ipaserver/data/pskc-mini.xml
new file mode 100644
index 000000000..e6ee7b55c
--- /dev/null
+++ b/ipatests/test_ipaserver/data/pskc-mini.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<KeyContainer xmlns="urn:ietf:params:xml:ns:keyprov:pskc" Version="1.0">
+ <KeyPackage/>
+</KeyContainer>
diff --git a/ipatests/test_ipaserver/test_otptoken_import.py b/ipatests/test_ipaserver/test_otptoken_import.py
new file mode 100644
index 000000000..7ee0754da
--- /dev/null
+++ b/ipatests/test_ipaserver/test_otptoken_import.py
@@ -0,0 +1,151 @@
+# Authors:
+# Nathaniel McCallum <npmccallum@redhat.com>
+#
+# Copyright (C) 2014 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import nose
+from nss import nss
+
+from ipaserver.install.ipa_otptoken_import import PSKCDocument, ValidationError
+
+basename = os.path.join(os.path.dirname(__file__), "data")
+
+class test_otptoken_import(object):
+ def test_figure3(self):
+ doc = PSKCDocument(os.path.join(basename, "pskc-figure3.xml"))
+ assert doc.keyname is None
+ assert [(t.id, t.options) for t in doc.getKeyPackages()] == \
+ [(u'12345678', {
+ 'ipatokenotpkey': u'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ',
+ 'ipatokenvendor': u'Manufacturer',
+ 'ipatokenserial': u'987654321',
+ 'ipatokenhotpcounter': 0L,
+ 'ipatokenotpdigits': 8,
+ 'type': u'hotp',
+ })]
+
+ def test_figure4(self):
+ doc = PSKCDocument(os.path.join(basename, "pskc-figure4.xml"))
+ assert doc.keyname is None
+ try:
+ [(t.id, t.options) for t in doc.getKeyPackages()]
+ except ValidationError: # Referenced keys are not supported.
+ pass
+ else:
+ assert False
+
+ def test_figure5(self):
+ doc = PSKCDocument(os.path.join(basename, "pskc-figure5.xml"))
+ assert doc.keyname is None
+ try:
+ [(t.id, t.options) for t in doc.getKeyPackages()]
+ except ValidationError: # PIN Policy is not supported.
+ pass
+ else:
+ 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': 0L,
+ 'ipatokenotpdigits': 8,
+ 'type': u'hotp'})]
+ finally:
+ nss.nss_shutdown()
+
+ 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()
+
+ 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()]
+ except ValidationError: # Unsupported token type.
+ 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': 0L,
+ 'ipatokenmodel': u'Model',
+ 'ipatokenotpdigits': 8,
+ 'type': u'hotp',
+ })]
+ finally:
+ nss.nss_shutdown()