1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
test_pskc2csv.doctest - tests for the pskc2csv script
Copyright (C) 2017 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.pskc2csv import main
Sadly we cannot test --help and --version properly because argparse calls
exit(0) which doctest does not like.
>>> sys.argv = shlex.split('pskc2csv --help')
>>> main() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
SystemExit: 0
>>> sys.argv = shlex.split('pskc2csv --version')
>>> main() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
SystemExit: 0
We hack stdin to lie about being a TTY to ensure the password propmpt are
always presented.
>>> class TTYFile(object):
... def __init__(self, f):
... self.f = f
...
... def isatty(self):
... return True
...
... def __getattr__(self, attr):
... return getattr(self.f, attr)
>>> sys.stdin = TTYFile(sys.stdin)
>>> sys.stdin.isatty()
True
We can output a CSV file with some columns with the just default arguments.
>>> sys.argv = shlex.split('pskc2csv tests/rfc6030/figure5.pskcxml')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
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,
We can also save the output to a file.
>>> f = tempfile.NamedTemporaryFile()
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure5.pskcxml --output') + [f.name]
>>> main()
>>> with open(f.name, 'r') as r:
... x = sys.stdout.write(r.read()) #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
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,
We can specify the columns to output with the --columns option and this also
allows specifying custom CSV file column headers.
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure5.pskcxml' +
... ' --columns id:NUMBER,secret,counter,policy.pin_min_length')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
NUMBER,secret,counter,policy.pin_min_length
12345678,3132333435363738393031323334353637383930,0,4
123456781,31323334,,
For password-encrypted files we should be prompted on the command line for a
password if the --password option was not specified. The prompt should
include the key name if one is present in the PSKC file. The --password
option can specify a literal password on the command line of point to a file
containing the password.
>>> getpass.getpass = lambda x: 'qwerty' if 'My Password 1' in x else 'WRONG'
>>> sys.argv = shlex.split('pskc2csv tests/rfc6030/figure7.pskcxml')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
>>> getpass.getpass = lambda x: 'WRONGPASSWORD'
>>> sys.argv = shlex.split('pskc2csv tests/rfc6030/figure7.pskcxml')
>>> main() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
DecryptionError: ...
>>> f = tempfile.NamedTemporaryFile()
>>> pskc = PSKC()
>>> key = pskc.add_key(secret='1234')
>>> pskc.encryption.setup_pbkdf2('qwerty')
>>> pskc.write(f.name)
>>> getpass.getpass = lambda x: 'qwerty'
>>> sys.argv = shlex.split('pskc2csv') + [f.name]
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
,31323334,,,
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure7.pskcxml --password qwerty')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
>>> f = tempfile.NamedTemporaryFile()
>>> with open(f.name, 'w') as f2: # open second file to keep tempfile
... x = f2.write('qwerty\n')
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure7.pskcxml --password') + [f.name]
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
For PSKC files that are encrypted with a pre-shared key we can use --secret
option to either supply a hex-encoded secret or point to a file name that
holds the secret.
>>> sys.argv = shlex.split('pskc2csv tests/rfc6030/figure6.pskcxml')
>>> main() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
KeyDerivationError: ...
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure6.pskcxml' +
... ' --secret 12345678901234567890123456789012')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
>>> f = tempfile.NamedTemporaryFile()
>>> with open(f.name, 'wb') as f2: # open second file to keep tempfile
... x = f2.write(a2b_hex('12345678901234567890123456789012'))
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure6.pskcxml --secret') + [f.name]
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
987654321,3132333435363738393031323334353637383930,urn:ietf:params:xml:ns:keyprov:pskc:hotp,8,
The --secret-encoding option can be used to specify the output encoding of
the secret (HEX, BASE64 or BASE32).
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure5.pskcxml' +
... ' -c serial,secret -e base64')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret
987654321,MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
987654321,MTIzNA==
>>> sys.argv = shlex.split(
... 'pskc2csv tests/rfc6030/figure5.pskcxml' +
... ' -c serial,secret -e base32')
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret
987654321,GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ
987654321,GEZDGNA=
Corner-case test: we an also handle empty PSKC files.
>>> f = tempfile.NamedTemporaryFile()
>>> with open(f.name, 'w') as f2: # open second file to keep tempfile
... x = f2.write('''
... <?xml version="1.0"?>
... <KeyContainer xmlns="urn:ietf:params:xml:ns:keyprov:pskc" Version="1.0">
... <KeyPackage/>
... </KeyContainer>
... '''.strip())
>>> sys.argv = shlex.split('pskc2csv') + [f.name]
>>> main() #doctest: +REPORT_UDIFF +NORMALIZE_WHITESPACE
serial,secret,algorithm,response_length,time_interval
|