lists.arthurdejong.org
RSS feed

python-pskc branch master updated. 1.0-19-g610f7cd

[Date Prev][Date Next] [Thread Prev][Thread Next]

python-pskc branch master updated. 1.0-19-g610f7cd



This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "python-pskc".

The branch, master has been updated
       via  610f7cd8c7d03e3ec370d0b17e2955750fb602b6 (commit)
       via  7bbaac30cdd78247c836c1349f5dcad23dd52c64 (commit)
       via  88002fc12d24add1876a169d2e310398c1348732 (commit)
       via  e91e498be862732d89392fa59c85045a92a584b0 (commit)
       via  c652eee8eca4ed5832f5befb67df0dd2332b607f (commit)
      from  ce96e69e8aeb311fbae44c16a12986c98e092cc9 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
https://arthurdejong.org/git/python-pskc/commit/?id=610f7cd8c7d03e3ec370d0b17e2955750fb602b6

commit 610f7cd8c7d03e3ec370d0b17e2955750fb602b6
Merge: ce96e69 7bbaac3
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Apr 21 22:54:50 2018 +0200

    Implement csv2pcks script


https://arthurdejong.org/git/python-pskc/commit/?id=7bbaac30cdd78247c836c1349f5dcad23dd52c64

commit 7bbaac30cdd78247c836c1349f5dcad23dd52c64
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Thu Apr 19 19:01:44 2018 +0200

    Add --skip-columns option
    
    This option can be used to skip a number of rows in the CSV file before
    the key data is read. If the number of rows to skip is 0, the column
    interpretation should be provided using the --columns option.

diff --git a/docs/csv2pskc.rst b/docs/csv2pskc.rst
index 6f70cfb..e177136 100644
--- a/docs/csv2pskc.rst
+++ b/docs/csv2pskc.rst
@@ -51,6 +51,14 @@ Options
    properties (e.g. use of ``id+serial`` sets both the ID and device serial
    number to the value found in that column).
 
+.. option:: --skip-rows N
+
+   By default the first row is treated as a header which contains labels.
+   This option can be used to either skip more row (the first row of the CSV 
file will
+   still be treated as a header) or to indicate that there is no header row.
+
+   In the latter case the :option:`--columns` option is required.
+
 .. option:: -x COL=VALUE, --set COL=VALUE
 
    Specify properties that are added to all keys in the generated PSKC file.
diff --git a/pskc/scripts/csv2pskc.py b/pskc/scripts/csv2pskc.py
index 327e169..e1cf7b6 100644
--- a/pskc/scripts/csv2pskc.py
+++ b/pskc/scripts/csv2pskc.py
@@ -56,6 +56,9 @@ parser.add_argument(
     '-c', '--columns', metavar='COL|COL:LABEL,..',
     help='list of columns or label to column mapping to import')
 parser.add_argument(
+    '--skip-rows', metavar='N', type=int, default=1,
+    help='the number of rows before rows with key information start')
+parser.add_argument(
     '-x', '--set', metavar='COL=VALUE', action='append',
     type=lambda x: x.split('=', 1), dest='extra_columns',
     help='add an extra value that is added to all key containers')
@@ -111,7 +114,11 @@ def main():
     # open the CSV file
     csvfile = open_csvfile(open(args.input, 'r') if args.input else sys.stdin)
     # figure out the meaning of the columns
-    columns = [x.lower().replace(' ', '_') for x in next(csvfile)]
+    columns = []
+    if args.skip_rows > 0:
+        columns = [x.lower().replace(' ', '_') for x in next(csvfile)]
+        for i in range(args.skip_rows - 1):
+            next(csvfile)
     if args.columns:
         if ':' in args.columns:
             # --columns is a list of mappings
diff --git a/tests/test_csv2pskc.doctest b/tests/test_csv2pskc.doctest
index c01b32b..d67987c 100644
--- a/tests/test_csv2pskc.doctest
+++ b/tests/test_csv2pskc.doctest
@@ -439,6 +439,55 @@ to all keys in the PSKC file:
 </pskc:KeyContainer>
 
 
+The --skip-rows option can be used to either not use the first row to denote
+the key properties that are set (in which case the --columns option is
+mandatory) or skip more rows at the beginning of the file.
+
+>>> f = tempfile.NamedTemporaryFile('w+t')
+>>> x = f.write('''
+... 987654321,7c613e9c2194ff7da7f4770ab2ed712111fcbe95
+... 987654322,4be618e3459e936137994854bc3d2ebe46f3cce2
+... '''.lstrip())
+>>> f.flush()
+>>> sys.argv = ['csv2pskc', f.name, '--skip-rows=0', 
'--columns=id+serial,secret']
+>>> main()  #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +REPORT_UDIFF
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" 
Version="1.0">
+...
+  <pskc:Key Id="987654321">
+...
+     <pskc:PlainValue>fGE+nCGU/32n9HcKsu1xIRH8vpU=</pskc:PlainValue>
+...
+  <pskc:Key Id="987654322">
+...
+     <pskc:PlainValue>S+YY40Wek2E3mUhUvD0uvkbzzOI=</pskc:PlainValue>
+...
+</pskc:KeyContainer>
+
+>>> f = tempfile.NamedTemporaryFile('w+t')
+>>> x = f.write('''
+... id+serial,secret
+... IGNORED LINE
+... 987654321,7c613e9c2194ff7da7f4770ab2ed712111fcbe95
+... 987654322,4be618e3459e936137994854bc3d2ebe46f3cce2
+... '''.lstrip())
+>>> f.flush()
+>>> sys.argv = ['csv2pskc', f.name, '--skip-rows=2']
+>>> main()  #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +REPORT_UDIFF
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" 
Version="1.0">
+...
+  <pskc:Key Id="987654321">
+...
+     <pskc:PlainValue>fGE+nCGU/32n9HcKsu1xIRH8vpU=</pskc:PlainValue>
+...
+  <pskc:Key Id="987654322">
+...
+     <pskc:PlainValue>S+YY40Wek2E3mUhUvD0uvkbzzOI=</pskc:PlainValue>
+...
+</pskc:KeyContainer>
+
+
 We can encrypt the resulting PSKC file with a passphrase.
 
 >>> f = tempfile.NamedTemporaryFile('w+t')

https://arthurdejong.org/git/python-pskc/commit/?id=88002fc12d24add1876a169d2e310398c1348732

commit 88002fc12d24add1876a169d2e310398c1348732
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Thu Apr 5 18:57:39 2018 +0200

    Add --set option
    
    This option can be used to set key properties for all keys in the PSKC
    file.

diff --git a/docs/csv2pskc.rst b/docs/csv2pskc.rst
index 06aabce..6f70cfb 100644
--- a/docs/csv2pskc.rst
+++ b/docs/csv2pskc.rst
@@ -51,6 +51,14 @@ Options
    properties (e.g. use of ``id+serial`` sets both the ID and device serial
    number to the value found in that column).
 
+.. option:: -x COL=VALUE, --set COL=VALUE
+
+   Specify properties that are added to all keys in the generated PSKC file.
+   Accepted labels are the same as for the :option:`--columns` option.
+
+   This can be useful for setting the ``issuer``, ``manufacturer`` or
+   oter common properties globally.
+
 .. option:: -p PASS/FILE, --password PASS/FILE, --passwd PASS/FILE
 
    Encrypt the PSKC file with the specified password. If the argument refers
diff --git a/pskc/scripts/csv2pskc.py b/pskc/scripts/csv2pskc.py
index a815755..327e169 100644
--- a/pskc/scripts/csv2pskc.py
+++ b/pskc/scripts/csv2pskc.py
@@ -56,6 +56,10 @@ parser.add_argument(
     '-c', '--columns', metavar='COL|COL:LABEL,..',
     help='list of columns or label to column mapping to import')
 parser.add_argument(
+    '-x', '--set', metavar='COL=VALUE', action='append',
+    type=lambda x: x.split('=', 1), dest='extra_columns',
+    help='add an extra value that is added to all key containers')
+parser.add_argument(
     '-p', '--password', '--passwd', metavar='PASS/FILE',
     help='password to use for encrypting the PSKC file)')
 parser.add_argument(
@@ -123,7 +127,7 @@ def main():
     # store rows in PSKC structure
     pskcfile = pskc.PSKC()
     for row in csvfile:
-        data = {}
+        data = dict(args.extra_columns or [])
         for column, value in zip(columns, row):
             for key in column.split('+'):
                 if value and key not in ('', '-'):
diff --git a/tests/test_csv2pskc.doctest b/tests/test_csv2pskc.doctest
index 0c6e8d5..c01b32b 100644
--- a/tests/test_csv2pskc.doctest
+++ b/tests/test_csv2pskc.doctest
@@ -392,6 +392,53 @@ file to key properties.
 </pskc:KeyContainer>
 
 
+We can also set global key properties with the --set option to apply values
+to all keys in the PSKC file:
+
+>>> f = tempfile.NamedTemporaryFile('w+t')
+>>> x = f.write('''
+... id+serial,secret
+... 987654321,7c613e9c2194ff7da7f4770ab2ed712111fcbe95
+... 987654322,4be618e3459e936137994854bc3d2ebe46f3cce2
+... '''.lstrip())
+>>> f.flush()
+>>> sys.argv = [
+...     'csv2pskc', f.name,
+...     '--set', 'manufacturer=TokenVendor', '--set', 'issuer=TokenIssuer']
+>>> main()  #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" 
Version="1.0">
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:Manufacturer>TokenVendor</pskc:Manufacturer>
+   <pskc:SerialNo>987654321</pskc:SerialNo>
+  </pskc:DeviceInfo>
+  <pskc:Key Id="987654321">
+   <pskc:Issuer>TokenIssuer</pskc:Issuer>
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>fGE+nCGU/32n9HcKsu1xIRH8vpU=</pskc:PlainValue>
+    </pskc:Secret>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:Manufacturer>TokenVendor</pskc:Manufacturer>
+   <pskc:SerialNo>987654322</pskc:SerialNo>
+  </pskc:DeviceInfo>
+  <pskc:Key Id="987654322">
+   <pskc:Issuer>TokenIssuer</pskc:Issuer>
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>S+YY40Wek2E3mUhUvD0uvkbzzOI=</pskc:PlainValue>
+    </pskc:Secret>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+</pskc:KeyContainer>
+
+
 We can encrypt the resulting PSKC file with a passphrase.
 
 >>> f = tempfile.NamedTemporaryFile('w+t')

https://arthurdejong.org/git/python-pskc/commit/?id=e91e498be862732d89392fa59c85045a92a584b0

commit e91e498be862732d89392fa59c85045a92a584b0
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Thu Apr 5 18:24:48 2018 +0200

    Add --columns option
    
    This option can be used to override the list of columns as found in the
    first line of the CSV file or provide a mapping for values found in the
    first line to PSKC properties.

diff --git a/docs/csv2pskc.rst b/docs/csv2pskc.rst
index 1c82a9f..06aabce 100644
--- a/docs/csv2pskc.rst
+++ b/docs/csv2pskc.rst
@@ -32,6 +32,25 @@ Options
    By default :program:`csv2pskc` writes a PSKC file to stdout. This option
    can be used to save to a file instead.
 
+.. option:: -c COL,COL,.., --columns COL,COL,..
+
+   Specify the meaning of the columns in the CSV file. By default the first
+   row of the CSV file is expected to list the names of the columns.
+
+   Any property of :class:`~pskc.key.Key` instances can be used as well as
+   :class:`~pskc.policy.Policy` properties via ``policy``. For example:
+   ``serial``, ``secret``, ``counter``, ``time_offset``, ``time_interval``,
+   ``interval``, ``time_drift``, ``issuer``, ``manufacturer``,
+   ``response_length``, ``policy.pin_min_length``.
+
+   This option can either specify a list of columns or a COL:KEY mapping
+   where COL refers to the value found in the first line of the CSV file and
+   KEY refers to a property as described above.
+
+   It is possible to map a single column in the CSV file to multiple PSKC
+   properties (e.g. use of ``id+serial`` sets both the ID and device serial
+   number to the value found in that column).
+
 .. option:: -p PASS/FILE, --password PASS/FILE, --passwd PASS/FILE
 
    Encrypt the PSKC file with the specified password. If the argument refers
diff --git a/pskc/scripts/csv2pskc.py b/pskc/scripts/csv2pskc.py
index 814e756..a815755 100644
--- a/pskc/scripts/csv2pskc.py
+++ b/pskc/scripts/csv2pskc.py
@@ -53,6 +53,9 @@ parser.add_argument(
     '-o', '--output', metavar='FILE',
     help='write PSKC to file instead of stdout')
 parser.add_argument(
+    '-c', '--columns', metavar='COL|COL:LABEL,..',
+    help='list of columns or label to column mapping to import')
+parser.add_argument(
     '-p', '--password', '--passwd', metavar='PASS/FILE',
     help='password to use for encrypting the PSKC file)')
 parser.add_argument(
@@ -103,14 +106,28 @@ def main():
     args = parser.parse_args()
     # open the CSV file
     csvfile = open_csvfile(open(args.input, 'r') if args.input else sys.stdin)
-    columns = next(csvfile)
+    # figure out the meaning of the columns
+    columns = [x.lower().replace(' ', '_') for x in next(csvfile)]
+    if args.columns:
+        if ':' in args.columns:
+            # --columns is a list of mappings
+            mapping = dict(
+                (label.lower().replace(' ', '_'), key.lower())
+                for label, key in (
+                    column.split(':')
+                    for column in args.columns.split(',')))
+            columns = [mapping.get(column, column) for column in columns]
+        else:
+            # --columns is a list of columns
+            columns = [x.lower() for x in args.columns.split(',')]
     # store rows in PSKC structure
     pskcfile = pskc.PSKC()
     for row in csvfile:
-        data = dict(
-            (key, from_column(key, value, args))
-            for key, value in zip(columns, row)
-            if value and key not in ('', '-'))
+        data = {}
+        for column, value in zip(columns, row):
+            for key in column.split('+'):
+                if value and key not in ('', '-'):
+                    data[key] = from_column(key, value, args)
         pskcfile.add_key(**data)
     # encrypt the file if needed
     if args.secret:
diff --git a/tests/test_csv2pskc.doctest b/tests/test_csv2pskc.doctest
index afa82e2..0c6e8d5 100644
--- a/tests/test_csv2pskc.doctest
+++ b/tests/test_csv2pskc.doctest
@@ -306,6 +306,92 @@ automatically pick up tab-separated files.
 </pskc:KeyContainer>
 
 
+We can use the --columns option to override using the first row to specify
+the key properties.
+
+>>> f = tempfile.NamedTemporaryFile('w+t')
+>>> x = f.write('''
+... nr,key,start date,info
+... 121232,6848464354638468468835346896846846846846,2017-04-01,something
+... 213422,9843138168168196616849849634548496832446,2017-02-12,else
+... '''.lstrip())
+>>> f.flush()
+>>> sys.argv = ['csv2pskc', f.name, '--columns', 
'id+serial,secret,start_date,-']
+>>> main()  #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" 
Version="1.0">
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:SerialNo>121232</pskc:SerialNo>
+   <pskc:StartDate>2017-04-01T00:00:00</pskc:StartDate>
+  </pskc:DeviceInfo>
+  <pskc:Key Id="121232">
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>aEhGQ1RjhGhGiDU0aJaEaEaEaEY=</pskc:PlainValue>
+    </pskc:Secret>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:SerialNo>213422</pskc:SerialNo>
+   <pskc:StartDate>2017-02-12T00:00:00</pskc:StartDate>
+  </pskc:DeviceInfo>
+  <pskc:Key Id="213422">
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>mEMTgWgWgZZhaEmEljRUhJaDJEY=</pskc:PlainValue>
+    </pskc:Secret>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+</pskc:KeyContainer>
+
+
+Alternatively, we can provide a mapping for column names found in the CSV
+file to key properties.
+
+>>> f = tempfile.NamedTemporaryFile('w+t')
+>>> x = f.write('''
+... nr,key,start date,info
+... 121232,6848464354638468468835346896846846846846,2017-04-01,something
+... 213422,9843138168168196616849849634548496832446,2017-02-12,else
+... '''.lstrip())
+>>> f.flush()
+>>> sys.argv = ['csv2pskc', f.name, '--columns', 
'key:secret,nr:id+serial,info:-']
+>>> main()  #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" 
Version="1.0">
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:SerialNo>121232</pskc:SerialNo>
+   <pskc:StartDate>2017-04-01T00:00:00</pskc:StartDate>
+  </pskc:DeviceInfo>
+  <pskc:Key Id="121232">
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>aEhGQ1RjhGhGiDU0aJaEaEaEaEY=</pskc:PlainValue>
+    </pskc:Secret>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:SerialNo>213422</pskc:SerialNo>
+   <pskc:StartDate>2017-02-12T00:00:00</pskc:StartDate>
+  </pskc:DeviceInfo>
+  <pskc:Key Id="213422">
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>mEMTgWgWgZZhaEmEljRUhJaDJEY=</pskc:PlainValue>
+    </pskc:Secret>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+</pskc:KeyContainer>
+
+
 We can encrypt the resulting PSKC file with a passphrase.
 
 >>> f = tempfile.NamedTemporaryFile('w+t')

https://arthurdejong.org/git/python-pskc/commit/?id=c652eee8eca4ed5832f5befb67df0dd2332b607f

commit c652eee8eca4ed5832f5befb67df0dd2332b607f
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 31 21:55:11 2018 +0200

    Add a csv2pskc script for CSV to PSKC conversion
    
    This script reads a CSV file and writes out a PSKC file with the key
    information from the CSV file. The CSV file is expected to have one row
    for each key and key property values in columns.

diff --git a/csv2pskc.py b/csv2pskc.py
new file mode 100755
index 0000000..65a7f2b
--- /dev/null
+++ b/csv2pskc.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+# csv2pskc.py - script to convert a CSV file to PSKC
+#
+# Copyright (C) 2018 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+from pskc.scripts import csv2pskc
+
+
+if __name__ == '__main__':  # pragma: no cover
+    csv2pskc.main()
diff --git a/docs/conf.py b/docs/conf.py
index e6e624a..f5da4c8 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -187,6 +187,8 @@ man_pages = [
      [u'Arthur de Jong'], 1),
     ('pskc2pskc', 'pskc2pskc', u'Convert a PSKC file to standard format',
      [u'Arthur de Jong'], 1),
+    ('csv2pskc', 'csv2pskc', u'Convert a CSV file to PSKC',
+     [u'Arthur de Jong'], 1),
 ]
 
 # If true, show URL addresses after external links.
diff --git a/docs/csv2pskc.rst b/docs/csv2pskc.rst
new file mode 100644
index 0000000..1c82a9f
--- /dev/null
+++ b/docs/csv2pskc.rst
@@ -0,0 +1,49 @@
+:orphan:
+
+csv2pskc
+========
+
+Synopsis
+--------
+
+**csv2pskc** [*options*] [<*FILE*>]
+
+Description
+-----------
+
+:program:`csv2pskc` reads a CSV file where the first line contains column
+labels and following lines contain key information for one key per line.
+
+Options
+-------
+
+.. program:: csv2pskc
+
+.. option:: -h, --help
+
+   Display usage summary.
+
+.. option:: -V, --version
+
+   Display version information.
+
+.. option:: -o FILE, --output FILE
+
+   By default :program:`csv2pskc` writes a PSKC file to stdout. This option
+   can be used to save to a file instead.
+
+.. option:: -p PASS/FILE, --password PASS/FILE, --passwd PASS/FILE
+
+   Encrypt the PSKC file with the specified password. If the argument refers
+   to a file the password is read from the file instead.
+
+.. option:: -s KEY/FILE, --secret KEY/FILE
+
+   A hex encoded encryption key or a file containing the binary key (raw
+   data, not encoded).
+
+.. option:: -e ENCODING, --secret-encoding ENCODING
+
+   Specify the encoding to use for reading key material from the CSV file. By
+   default HEX encoding is used. Valid encodings are: ``base32``, ``base64``
+   or ``hex``.
diff --git a/pskc/scripts/csv2pskc.py b/pskc/scripts/csv2pskc.py
new file mode 100644
index 0000000..814e756
--- /dev/null
+++ b/pskc/scripts/csv2pskc.py
@@ -0,0 +1,122 @@
+# csv2pskc.py - script to convert a CSV file to PSKC
+#
+# Copyright (C) 2018 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""Script to convert a CSV file to PSKC."""
+
+import argparse
+import base64
+import csv
+import sys
+from binascii import a2b_hex
+
+import dateutil.parser
+
+import pskc
+from pskc.scripts.util import (
+    OutputFile, VersionAction, get_key, get_password)
+
+
+epilog = '''
+supported columns:
+  id, serial, secret, counter, time_offset, time_interval, interval,
+  time_drift, issuer, manufacturer, response_length, algorithm
+And any other properties of pskc.key.Key instances.
+
+Report bugs to <python-pskc-users@lists.arthurdejong.org>.
+'''.strip()
+
+# set up command line parser
+parser = argparse.ArgumentParser(
+    formatter_class=argparse.RawDescriptionHelpFormatter,
+    description='Convert a CSV file to PSKC.', epilog=epilog)
+parser.add_argument(
+    'input', nargs='?', metavar='FILE', help='the CSV file to read')
+parser.add_argument(
+    '-V', '--version', action=VersionAction)
+parser.add_argument(
+    '-o', '--output', metavar='FILE',
+    help='write PSKC to file instead of stdout')
+parser.add_argument(
+    '-p', '--password', '--passwd', metavar='PASS/FILE',
+    help='password to use for encrypting the PSKC file)')
+parser.add_argument(
+    '-s', '--secret', metavar='KEY/FILE',
+    help='hex encoded encryption key or a file containing the binary key')
+encodings = {
+    'hex': a2b_hex,
+    'base32': base64.b32decode,
+    'base64': base64.b64decode,
+}
+parser.add_argument(
+    '-e', '--secret-encoding', choices=sorted(encodings.keys()),
+    help='encoding used for reading key material',
+    default='hex')
+
+
+def from_column(key, value, args):
+    """Convert a key value read from a CSV file in a format for PSKC."""
+    # decode encoded secret
+    if key == 'secret':
+        return encodings[args.secret_encoding](value)
+    # convert dates to timestamps
+    if key.endswith('_date'):
+        return dateutil.parser.parse(value)
+    return value
+
+
+def open_csvfile(inputfile):
+    """Open the CSV file, trying to detect the dialect."""
+    # Guess dialect if possible and open the CSV file
+    dialect = 'excel'
+    try:
+        # seek before read to skip sniffing on non-seekable files
+        inputfile.seek(0)
+        try:
+            dialect = csv.Sniffer().sniff(inputfile.read(1024))
+        except Exception:  # pragma: no cover (very hard to test in doctest)
+            pass
+        inputfile.seek(0)
+    except IOError:  # pragma: no cover (very hard to test in doctest)
+        pass
+    return csv.reader(inputfile, dialect)
+
+
+def main():
+    """Convert a CSV file to PSKC."""
+    # parse command-line arguments
+    args = parser.parse_args()
+    # open the CSV file
+    csvfile = open_csvfile(open(args.input, 'r') if args.input else sys.stdin)
+    columns = next(csvfile)
+    # store rows in PSKC structure
+    pskcfile = pskc.PSKC()
+    for row in csvfile:
+        data = dict(
+            (key, from_column(key, value, args))
+            for key, value in zip(columns, row)
+            if value and key not in ('', '-'))
+        pskcfile.add_key(**data)
+    # encrypt the file if needed
+    if args.secret:
+        pskcfile.encryption.setup_preshared_key(key=get_key(args.secret))
+    elif args.password:
+        pskcfile.encryption.setup_pbkdf2(get_password(args.password))
+    # write output PSKC file
+    with OutputFile(args.output) as output:
+        pskcfile.write(output)
diff --git a/setup.py b/setup.py
index 261db78..297179a 100755
--- a/setup.py
+++ b/setup.py
@@ -76,6 +76,7 @@ setup(
     },
     entry_points={
         'console_scripts': [
+            'csv2pskc = pskc.scripts.csv2pskc:main',
             'pskc2csv = pskc.scripts.pskc2csv:main',
             'pskc2pskc = pskc.scripts.pskc2pskc:main',
         ],
diff --git a/tests/test_csv2pskc.doctest b/tests/test_csv2pskc.doctest
new file mode 100644
index 0000000..afa82e2
--- /dev/null
+++ b/tests/test_csv2pskc.doctest
@@ -0,0 +1,421 @@
+test_csv2pskc.doctest - tests for the csv2pskc script
+
+Copyright (C) 2018 Arthur de Jong
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library 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
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA
+
+
+>>> from binascii import a2b_hex
+>>> import getpass
+>>> import shlex
+>>> import sys
+>>> import tempfile
+
+>>> from pskc import PSKC
+>>> from pskc.scripts.csv2pskc import main
+
+
+Sadly we cannot test --help and --version properly because argparse calls
+exit(0) which doctest does not like.
+
+>>> sys.argv = shlex.split('csv2pskc --help')
+>>> main()  # doctest: +IGNORE_EXCEPTION_DETAIL
+Traceback (most recent call last):
+    ...
+SystemExit: 0
+>>> sys.argv = shlex.split('csv2pskc --version')
+>>> main()  # doctest: +IGNORE_EXCEPTION_DETAIL
+Traceback (most recent call last):
+    ...
+SystemExit: 0
+
+
+We can output a PSKC file with some simple data from a CSV file. The columns
+in the CSV file refer to names of PSKC properties.
+
+>>> f = tempfile.NamedTemporaryFile('w+t')
+>>> x = f.write('''
+... serial,secret,algorithm,response_length,time_interval
+... 
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
+... 987654321,31323334,urn:ietf:params:xml:ns:keyprov:pskc:pin,4,
+... '''.lstrip())
+>>> f.flush()
+>>> sys.argv = ['csv2pskc', f.name]
+>>> main()  #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" 
Version="1.0">
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:SerialNo>987654321</pskc:SerialNo>
+  </pskc:DeviceInfo>
+  <pskc:Key Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp">
+   <pskc:AlgorithmParameters>
+    <pskc:ResponseFormat Length="8"/>
+   </pskc:AlgorithmParameters>
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=</pskc:PlainValue>
+    </pskc:Secret>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:SerialNo>987654321</pskc:SerialNo>
+  </pskc:DeviceInfo>
+  <pskc:Key Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:pin">
+   <pskc:AlgorithmParameters>
+    <pskc:ResponseFormat Length="4"/>
+   </pskc:AlgorithmParameters>
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNA==</pskc:PlainValue>
+    </pskc:Secret>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+</pskc:KeyContainer>
+
+
+We can also save the output to a file.
+
+>>> f = tempfile.NamedTemporaryFile('w+t')
+>>> x = f.write('''
+... serial,secret,algorithm,response_length,time_interval
+... 
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
+... 987654321,31323334,urn:ietf:params:xml:ns:keyprov:pskc:pin,4,
+... '''.lstrip())
+>>> f2 = tempfile.NamedTemporaryFile()
+>>> f.flush()
+>>> sys.argv = ['csv2pskc', f.name, '--output', f2.name]
+>>> main()
+>>> with open(f2.name, 'r') as r:
+...     x = sys.stdout.write(r.read())  #doctest: +ELLIPSIS +REPORT_UDIFF 
+NORMALIZE_WHITESPACE
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" 
Version="1.0">
+ <pskc:KeyPackage>
+...
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=</pskc:PlainValue>
+    </pskc:Secret>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+...
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNA==</pskc:PlainValue>
+    </pskc:Secret>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+</pskc:KeyContainer>
+
+
+A bigger example.
+
+>>> f = tempfile.NamedTemporaryFile('w+t')
+>>> x = f.write('''
+... 
serial,secret,algorithm,response_length,response_encoding,manufacturer,issuer,policy.start_date,policy.expiry_date,id,counter
+... 
654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,DECIMAL,TokenVendorAcme,Issuer,2006-05-01
 00:00:00+00:00,2006-05-31 00:00:00+00:00,1,2
+... 
123456,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,DECIMAL,TokenVendorAcme,Issuer,2006-05-01
 00:00:00+00:00,2006-05-31 00:00:00+00:00,2,3
+... 
9999999,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,DECIMAL,TokenVendorAcme,Issuer,2006-03-01
 00:00:00+00:00,2006-03-31 00:00:00+00:00,3,42
+... 
9999999,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,DECIMAL,TokenVendorAcme,Issuer,2006-04-01
 00:00:00+00:00,2006-04-30 00:00:00+00:00,4,12
+... '''.lstrip())
+>>> f.flush()
+>>> sys.argv = ['csv2pskc', f.name]
+>>> main()  #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" 
Version="1.0">
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:Manufacturer>TokenVendorAcme</pskc:Manufacturer>
+   <pskc:SerialNo>654321</pskc:SerialNo>
+  </pskc:DeviceInfo>
+  <pskc:Key Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp" Id="1">
+   <pskc:Issuer>Issuer</pskc:Issuer>
+   <pskc:AlgorithmParameters>
+    <pskc:ResponseFormat Encoding="DECIMAL" Length="8"/>
+   </pskc:AlgorithmParameters>
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=</pskc:PlainValue>
+    </pskc:Secret>
+    <pskc:Counter>
+     <pskc:PlainValue>2</pskc:PlainValue>
+    </pskc:Counter>
+   </pskc:Data>
+   <pskc:Policy>
+    <pskc:StartDate>2006-05-01T00:00:00Z</pskc:StartDate>
+    <pskc:ExpiryDate>2006-05-31T00:00:00Z</pskc:ExpiryDate>
+   </pskc:Policy>
+  </pskc:Key>
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:Manufacturer>TokenVendorAcme</pskc:Manufacturer>
+   <pskc:SerialNo>123456</pskc:SerialNo>
+  </pskc:DeviceInfo>
+  <pskc:Key Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp" Id="2">
+   <pskc:Issuer>Issuer</pskc:Issuer>
+   <pskc:AlgorithmParameters>
+    <pskc:ResponseFormat Encoding="DECIMAL" Length="8"/>
+   </pskc:AlgorithmParameters>
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=</pskc:PlainValue>
+    </pskc:Secret>
+    <pskc:Counter>
+     <pskc:PlainValue>3</pskc:PlainValue>
+    </pskc:Counter>
+   </pskc:Data>
+   <pskc:Policy>
+    <pskc:StartDate>2006-05-01T00:00:00Z</pskc:StartDate>
+    <pskc:ExpiryDate>2006-05-31T00:00:00Z</pskc:ExpiryDate>
+   </pskc:Policy>
+  </pskc:Key>
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:Manufacturer>TokenVendorAcme</pskc:Manufacturer>
+   <pskc:SerialNo>9999999</pskc:SerialNo>
+  </pskc:DeviceInfo>
+  <pskc:Key Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp" Id="3">
+   <pskc:Issuer>Issuer</pskc:Issuer>
+   <pskc:AlgorithmParameters>
+    <pskc:ResponseFormat Encoding="DECIMAL" Length="8"/>
+   </pskc:AlgorithmParameters>
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=</pskc:PlainValue>
+    </pskc:Secret>
+    <pskc:Counter>
+     <pskc:PlainValue>42</pskc:PlainValue>
+    </pskc:Counter>
+   </pskc:Data>
+   <pskc:Policy>
+    <pskc:StartDate>2006-03-01T00:00:00Z</pskc:StartDate>
+    <pskc:ExpiryDate>2006-03-31T00:00:00Z</pskc:ExpiryDate>
+   </pskc:Policy>
+  </pskc:Key>
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+  <pskc:DeviceInfo>
+   <pskc:Manufacturer>TokenVendorAcme</pskc:Manufacturer>
+   <pskc:SerialNo>9999999</pskc:SerialNo>
+  </pskc:DeviceInfo>
+  <pskc:Key Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp" Id="4">
+   <pskc:Issuer>Issuer</pskc:Issuer>
+   <pskc:AlgorithmParameters>
+    <pskc:ResponseFormat Encoding="DECIMAL" Length="8"/>
+   </pskc:AlgorithmParameters>
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=</pskc:PlainValue>
+    </pskc:Secret>
+    <pskc:Counter>
+     <pskc:PlainValue>12</pskc:PlainValue>
+    </pskc:Counter>
+   </pskc:Data>
+   <pskc:Policy>
+    <pskc:StartDate>2006-04-01T00:00:00Z</pskc:StartDate>
+    <pskc:ExpiryDate>2006-04-30T00:00:00Z</pskc:ExpiryDate>
+   </pskc:Policy>
+  </pskc:Key>
+ </pskc:KeyPackage>
+</pskc:KeyContainer>
+
+
+As long as it is in a file (does not work on stdin) the script should also
+automatically pick up tab-separated files.
+
+>>> f = tempfile.NamedTemporaryFile('w+t')
+>>> x = f.write('''
+... id\tsecret\tcounter
+... 654321\t3132333435363738393031323334353637383930\t2
+... 123456\t3132333435363738393031323334353637383930\t3
+... 9999999\t3132333435363738393031323334353637383930\t42
+... 9999999\t3132333435363738393031323334353637383930\t12
+... '''.lstrip())
+>>> f.flush()
+>>> sys.argv = ['csv2pskc', f.name]
+>>> main()  #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" 
Version="1.0">
+ <pskc:KeyPackage>
+  <pskc:Key Id="654321">
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=</pskc:PlainValue>
+    </pskc:Secret>
+    <pskc:Counter>
+     <pskc:PlainValue>2</pskc:PlainValue>
+    </pskc:Counter>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+  <pskc:Key Id="123456">
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=</pskc:PlainValue>
+    </pskc:Secret>
+    <pskc:Counter>
+     <pskc:PlainValue>3</pskc:PlainValue>
+    </pskc:Counter>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+  <pskc:Key Id="9999999">
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=</pskc:PlainValue>
+    </pskc:Secret>
+    <pskc:Counter>
+     <pskc:PlainValue>42</pskc:PlainValue>
+    </pskc:Counter>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+  <pskc:Key Id="9999999">
+   <pskc:Data>
+    <pskc:Secret>
+     <pskc:PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=</pskc:PlainValue>
+    </pskc:Secret>
+    <pskc:Counter>
+     <pskc:PlainValue>12</pskc:PlainValue>
+    </pskc:Counter>
+   </pskc:Data>
+  </pskc:Key>
+ </pskc:KeyPackage>
+</pskc:KeyContainer>
+
+
+We can encrypt the resulting PSKC file with a passphrase.
+
+>>> f = tempfile.NamedTemporaryFile('w+t')
+>>> x = f.write('''
+... id,secret
+... 987654321,7c613e9c2194ff7da7f4770ab2ed712111fcbe95
+... 987654322,4be618e3459e936137994854bc3d2ebe46f3cce2
+... '''.lstrip())
+>>> f.flush()
+>>> sys.argv = ['csv2pskc', f.name, '--passwd', 'supersecure']
+>>> main()  #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +REPORT_UDIFF
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" 
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"; 
xmlns:xenc11="http://www.w3.org/2009/xmlenc11#"; Version="1.0">
+ <pskc:EncryptionKey>
+  <xenc11:DerivedKey>
+   <xenc11:KeyDerivationMethod 
Algorithm="http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2";>
+    <xenc11:PBKDF2-params>
+     <Salt>
+      <Specified>...</Specified>
+     </Salt>
+     <IterationCount>...</IterationCount>
+     <KeyLength>16</KeyLength>
+    </xenc11:PBKDF2-params>
+   </xenc11:KeyDerivationMethod>
+  </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>...</xenc:CipherValue>
+   </xenc:CipherData>
+  </pskc:MACKey>
+ </pskc:MACMethod>
+ <pskc:KeyPackage>
+...
+    <pskc:Secret>
+     <pskc:EncryptedValue>
+      <xenc:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+      <xenc:CipherData>
+       <xenc:CipherValue>...</xenc:CipherValue>
+      </xenc:CipherData>
+     </pskc:EncryptedValue>
+     <pskc:ValueMAC>...</pskc:ValueMAC>
+    </pskc:Secret>
+...
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+...
+    <pskc:Secret>
+     <pskc:EncryptedValue>
+      <xenc:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+      <xenc:CipherData>
+       <xenc:CipherValue>...</xenc:CipherValue>
+      </xenc:CipherData>
+     </pskc:EncryptedValue>
+     <pskc:ValueMAC>...</pskc:ValueMAC>
+    </pskc:Secret>
+...
+ </pskc:KeyPackage>
+</pskc:KeyContainer>
+
+
+Alternatively we can switch from passphrase-based encryption to key-based
+encryption.
+
+>>> f = tempfile.NamedTemporaryFile('w+t')
+>>> x = f.write('''
+... id,secret
+... 987654321,7c613e9c2194ff7da7f4770ab2ed712111fcbe95
+... 987654322,4be618e3459e936137994854bc3d2ebe46f3cce2
+... '''.lstrip())
+>>> f.flush()
+>>> sys.argv = ['csv2pskc', f.name, '--secret', 
'12345678901234567890123456789012']
+>>> main()  #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +REPORT_UDIFF
+<?xml version="1.0" encoding="UTF-8"?>
+<pskc:KeyContainer xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" 
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"; Version="1.0">
+ <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>...</xenc:CipherValue>
+   </xenc:CipherData>
+  </pskc:MACKey>
+ </pskc:MACMethod>
+ <pskc:KeyPackage>
+...
+    <pskc:Secret>
+     <pskc:EncryptedValue>
+      <xenc:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+      <xenc:CipherData>
+       <xenc:CipherValue>...</xenc:CipherValue>
+      </xenc:CipherData>
+     </pskc:EncryptedValue>
+     <pskc:ValueMAC>...</pskc:ValueMAC>
+    </pskc:Secret>
+...
+ </pskc:KeyPackage>
+ <pskc:KeyPackage>
+...
+    <pskc:Secret>
+     <pskc:EncryptedValue>
+      <xenc:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+      <xenc:CipherData>
+       <xenc:CipherValue>...</xenc:CipherValue>
+      </xenc:CipherData>
+     </pskc:EncryptedValue>
+     <pskc:ValueMAC>...</pskc:ValueMAC>
+    </pskc:Secret>
+...
+ </pskc:KeyPackage>
+</pskc:KeyContainer>

-----------------------------------------------------------------------

Summary of changes:
 pskc2pskc.py => csv2pskc.py |   6 +-
 docs/conf.py                |   2 +
 docs/csv2pskc.rst           |  84 ++++++
 pskc/scripts/csv2pskc.py    | 150 +++++++++++
 setup.py                    |   1 +
 tests/test_csv2pskc.doctest | 603 ++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 843 insertions(+), 3 deletions(-)
 copy pskc2pskc.py => csv2pskc.py (87%)
 create mode 100644 docs/csv2pskc.rst
 create mode 100644 pskc/scripts/csv2pskc.py
 create mode 100644 tests/test_csv2pskc.doctest


hooks/post-receive
-- 
python-pskc
-- 
To unsubscribe send an email to
python-pskc-commits-unsubscribe@lists.arthurdejong.org or see
https://lists.arthurdejong.org/python-pskc-commits/