-
Notifications
You must be signed in to change notification settings - Fork 20
/
main.py
executable file
·307 lines (277 loc) · 11.1 KB
/
main.py
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# main.py
#
version = '1.0.7'
import getopt
from getpass import getpass
import os
import re
import sys
from vmwarevmx import VMwareVMX
def getpassword(text):
try:
while True:
password = getpass(text)
if password != '':
return password
sys.stderr.write('Error: Empty password is not allowed. Try again.\n')
except (EOFError, KeyboardInterrupt):
sys.exit('\nError: Need a password')
def initgetopt(name, options, files=''):
maxlen = max(len(long)
for (short, long, arg, text) in options)
optlong = list(long + '=' if arg else long
for (short, long, arg, text) in options)
optshort = ''.join(short + ':' if arg else short
for (short, long, arg, text) in options)
withoutarg = ''.join(short
for (short, long, arg, text) in options if not arg)
witharg = ''.join('[-{s} {a}] '.format(s=short, a=arg)
for (short, long, arg, text) in options if arg)
usage = '\n'.join(' -{s}, --{l:{len}s} {t}' \
.format(s=short, l=long, t=text, len=maxlen)
for (short, long, arg, text) in options)
usage = 'Usage: {n} [-{wo}] {w}{f}\n{u}' \
.format(n=name, wo=withoutarg, w=witharg, f=files, u=usage)
return optshort, optlong, usage
def main(argv):
addfilename = None
changepassword = False
cipher = None
decrypt = False
displayname = None
encrypt = False
force = False
guestOSdetaileddata = None
guestInfodetaileddata = None
hash_rounds = None
ignore = False
new = False
outfilename = None
password = None
removefilename = None
options = [
('a', 'add', 'file',
'add line(s) from file'),
('c', 'change', '',
'change password'),
('d', 'decrypt', '',
'decrypt in_file (default)'),
('D', 'displayname', 'name',
'set the displayname parameter'),
('e', 'encrypt', '',
'encrypt in_file'),
('f', 'force', '',
'force overwriting out_file'),
('g', 'guestos', '',
'set the guestOS parameter'),
('G', 'guestinfo', '',
'set the guestInfo parameter'),
('h', 'help', '',
'display this message'),
('i', 'ignore', '',
'ignore some errors preventing decryption of a corrupted in_file'),
('n', 'new', '',
'after decrypt, use new parameters for encryption'),
('p', 'password', 'password',
'set the password (default: ask for it)'),
('r', 'remove', 'file',
'remove line(s) found in file'),
('v', 'version', '',
'print the version string and exit'),
('x', 'hashrounds', 'value',
'used for the number of hash rounds of the password (default: 10,000)'),
('1', 'aes', '',
'encrypt with old AES-256 algorithm'),
('2', 'xts', '',
'encrypt with new XTS-AES-256 algorithm'),
]
(optshort, optlong, usage) = initgetopt(os.path.basename(argv[0]),
options,
'in_file [out_file]')
try:
(opts, args) = getopt.getopt(argv[1:], optshort, optlong)
except getopt.GetoptError as err:
sys.stderr.write('Error: ' + str(err) + '\n')
sys.exit(usage)
for (opt, arg) in opts:
if opt in ('-a', '--add'):
addfilename = arg
elif opt in ('-c', '--change'):
changepassword, decrypt, encrypt = True, True, True
elif opt in ('-d', '--decrypt'):
decrypt = True
elif opt in ('-D', '--displayname'):
displayname = arg
elif opt in ('-e', '--encrypt'):
encrypt = True
elif opt in ('-f', '--force'):
force = True
elif opt in ('-g', '--guestos'):
guestOSdetaileddata = arg
elif opt in ('-G', '--guestinfo'):
guestInfodetaileddata = arg
elif opt in ('-h', '--help'):
sys.stderr.write(usage + '\n')
sys.exit(0)
elif opt in ('-i', '--ignore'):
ignore = True
elif opt in ('-n', '--new'):
new = True
elif opt in ('-p', '--password'):
password = arg
elif opt in ('-r', '--remove'):
removefilename = arg
elif opt in ('-v', '--version'):
print('VMwareVMX Crypto Tool v{v}\n' \
'Copyright (C) 2018-2024 Robert Federle'.format(v=version))
sys.exit(0)
elif opt in ('-x', '--hashrounds'):
try:
hash_rounds = int(arg)
if hash_rounds <= 0:
sys.exit('Error: hashrounds value must be a positive non-zero number')
except ValueError:
sys.exit('Error: hashrounds value must be a positive non-zero number')
elif opt in ('-1', '--aes'):
cipher = "AES-256"
elif opt in ('-2', '--xts'):
cipher = "XTS-AES-256"
else:
sys.exit(usage)
if len(args) == 0:
sys.exit('Error: No input file given')
# Read the configuration file
infilename = args[0]
if len(args) == 2:
outfilename = args[1]
if os.path.abspath(infilename) == os.path.abspath(outfilename):
sys.exit('Error: Input and output files are the same')
elif len(args) > 2:
sys.exit('Error: More arguments after filenames found')
try:
lines = open(infilename, "r").read().split('\n')
except (OSError, IOError) as err:
sys.exit('Error: Cannot read from file ' + infilename + ": " + str(err))
# Analyze the file and replace all header entries with the ones from the command-line
encoding = 'utf-8'
headerlist = []
configlist = []
keysafe, data = None, None
counter = 0
for line in lines:
if line == '':
continue
match = re.match('^.encoding *= *"(.+)"$', line)
if match:
encoding = match.group(1).lower()
headerlist.append(line)
continue
if re.match('^display[Nn]ame *= *"(.+)"$', line):
if displayname:
# Replace with new parameter
line = 'displayName = "{}"'.format(displayname)
headerlist.append(line)
continue
if re.match('^guestOS.detailed.data *= *"(.+)"$', line):
if guestOSdetaileddata:
# Replace with new parameter
line = 'guestOS.detailed.data = "{}"'.format(guestOSdetaileddata)
headerlist.append(line)
continue
if re.match('^guestInfo.detailed.data *= *"(.+)"$', line):
if guestInfodetaileddata:
# Replace with new parameter
line = 'guestInfo.detailed.data = "{}"'.format(guestInfodetaileddata)
headerlist.append(line)
continue
if re.match('^encryption.keySafe *= *"vmware:key/list/\(pair/\(phrase/(.+)pass2key(.+)\)\)"$', line):
keysafe = line
continue
if re.match('^encryption.data *= *"(.+)"$', line):
data = line
continue
configlist.append(line)
# If the file is encrypted, enforce decryption, otherwise nothing useful can be done
if (decrypt == False) and (keysafe and data):
decrypt = True
# If we add or remove lines from an encrypted file or a specific cipher is selected,
# make sure the result is also encrypted
if addfilename or removefilename or cipher:
encrypt = True
# Create VMwareVMX object
vmx = VMwareVMX.new()
# Decrypt the configuration
if decrypt:
if keysafe is None or data is None:
sys.exit('Error: File ' + infilename + ' is not a valid VMX file or already decrypted')
if password is None:
password = getpassword('Password:')
try:
config = vmx.decrypt(password, keysafe, data, encoding, ignore)
except ValueError as err:
sys.exit('Error: ' + str(err))
if config is None:
sys.exit('Error: Password is invalid')
else:
keysafe, data = None, None
configlist = config.split('\n')
# Remove lines from the configuration
if removefilename:
try:
removelist = open(removefilename, "r", encoding=encoding).read().split('\n')
except (OSError, IOError) as err:
sys.exit('Error: Cannot read from file ' + removefilename + ": " + str(err))
configlist = [x for x in configlist if x not in removelist]
# Add lines to the configuration
if addfilename:
try:
addlist = open(addfilename, "r", encoding=encoding).read().split('\n')
except (OSError, IOError) as err:
sys.exit('Error: Cannot read from file ' + addfilename + ": " + str(err))
configlist.extend(addlist)
# Use new parameters (hash_rounds, identifier, salt, AES IV, AES keys) for encryption?
if new:
vmx.reinit()
# Encrypt the configuration
if encrypt:
if keysafe and data:
sys.exit('Error: VMX file is already encrypted')
# If no password is given and password should be changed, ask for password
if changepassword or password is None:
password = getpassword('New Password:')
password2 = getpassword('New Password (again):')
if password != password2:
sys.exit("Error: Passwords don't match")
# Change the cipher algorithm if requested
if cipher:
vmx.cipher = cipher
# Create configuration for encryption
config = '\n'.join(configlist)
try:
(keysafe, data) = vmx.encrypt(password, config, hash_rounds)
except ValueError as err:
sys.exit('Error: '+ str(err))
# Create configuration
config = '\n'.join(headerlist) + '\n'
if keysafe and data:
config = config + keysafe + '\n' + data + '\n'
else:
config = config + '\n'.join(configlist) + '\n'
# Write to the configuration file or to stdout
if outfilename is None or outfilename == '-':
sys.stdout.write(config)
else:
if force is False and os.path.isfile(outfilename):
sys.exit('Error: File ' + outfilename + ' exists; ' \
'use --force to overwrite')
try:
open(outfilename, "w", encoding=encoding).write(config)
except (OSError, IOError) as err:
sys.exit('Error: Cannot write to file ' + outfilename + ": " + str(err))
sys.exit(0)
if __name__ == '__main__':
main(sys.argv)
# vim:set ts=4 sw=4 sts=4 expandtab: