1#!/usr/local/bin/python2
2#
3# Copyright (c) 2014 The FreeBSD Foundation
4# Copyright 2014 John-Mark Gurney
5# All rights reserved.
6#
7# This software was developed by John-Mark Gurney under
8# the sponsorship from the FreeBSD Foundation.
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions
11# are met:
12# 1.  Redistributions of source code must retain the above copyright
13#     notice, this list of conditions and the following disclaimer.
14# 2.  Redistributions in binary form must reproduce the above copyright
15#     notice, this list of conditions and the following disclaimer in the
16#     documentation and/or other materials provided with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28# SUCH DAMAGE.
29#
30# $FreeBSD: stable/11/tests/sys/opencrypto/cryptodev.py 350209 2019-07-22 08:27:44Z lwhsu $
31#
32
33import array
34import dpkt
35from fcntl import ioctl
36import os
37import platform
38import signal
39from struct import pack as _pack
40
41from cryptodevh import *
42
43__all__ = [ 'Crypto', 'MismatchError', ]
44
45class FindOp(dpkt.Packet):
46	__byte_order__ = '@'
47	__hdr__ = ( ('crid', 'i', 0),
48		('name', '32s', 0),
49	)
50
51class SessionOp(dpkt.Packet):
52	__byte_order__ = '@'
53	__hdr__ = ( ('cipher', 'I', 0),
54		('mac', 'I', 0),
55		('keylen', 'I', 0),
56		('key', 'P', 0),
57		('mackeylen', 'i', 0),
58		('mackey', 'P', 0),
59		('ses', 'I', 0),
60	)
61
62class SessionOp2(dpkt.Packet):
63	__byte_order__ = '@'
64	__hdr__ = ( ('cipher', 'I', 0),
65		('mac', 'I', 0),
66		('keylen', 'I', 0),
67		('key', 'P', 0),
68		('mackeylen', 'i', 0),
69		('mackey', 'P', 0),
70		('ses', 'I', 0),
71		('crid', 'i', 0),
72		('pad0', 'i', 0),
73		('pad1', 'i', 0),
74		('pad2', 'i', 0),
75		('pad3', 'i', 0),
76	)
77
78class CryptOp(dpkt.Packet):
79	__byte_order__ = '@'
80	__hdr__ = ( ('ses', 'I', 0),
81		('op', 'H', 0),
82		('flags', 'H', 0),
83		('len', 'I', 0),
84		('src', 'P', 0),
85		('dst', 'P', 0),
86		('mac', 'P', 0),
87		('iv', 'P', 0),
88	)
89
90class CryptAEAD(dpkt.Packet):
91	__byte_order__ = '@'
92	__hdr__ = (
93		('ses',		'I', 0),
94		('op',		'H', 0),
95		('flags',	'H', 0),
96		('len',		'I', 0),
97		('aadlen',	'I', 0),
98		('ivlen',	'I', 0),
99		('src',		'P', 0),
100		('dst',		'P', 0),
101		('aad',		'P', 0),
102		('tag',		'P', 0),
103		('iv',		'P', 0),
104	)
105
106# h2py.py can't handle multiarg macros
107CRIOGET = 3221513060
108CIOCGSESSION = 3224396645
109CIOCFSESSION = 2147771238
110CIOCKEY = 3230688104
111CIOCASYMFEAT = 1074029417
112CIOCKEY2 = 3230688107
113CIOCFINDDEV = 3223610220
114if platform.architecture()[0] == '64bit':
115    CIOCGSESSION2 = 3225445226
116    CIOCCRYPT = 3224396647
117    CIOCCRYPTAEAD = 3225445229
118else:
119    CIOCGSESSION2 = 3224396650
120    CIOCCRYPT = 3223085927
121    CIOCCRYPTAEAD = 3223872365
122
123def _getdev():
124	fd = os.open('/dev/crypto', os.O_RDWR)
125	buf = array.array('I', [0])
126	ioctl(fd, CRIOGET, buf, 1)
127	os.close(fd)
128
129	return buf[0]
130
131_cryptodev = _getdev()
132
133def _findop(crid, name):
134	fop = FindOp()
135	fop.crid = crid
136	fop.name = name
137	s = array.array('B', fop.pack_hdr())
138	ioctl(_cryptodev, CIOCFINDDEV, s, 1)
139	fop.unpack(s)
140
141	try:
142		idx = fop.name.index('\x00')
143		name = fop.name[:idx]
144	except ValueError:
145		name = fop.name
146
147	return fop.crid, name
148
149class Crypto:
150	@staticmethod
151	def findcrid(name):
152		return _findop(-1, name)[0]
153
154	@staticmethod
155	def getcridname(crid):
156		return _findop(crid, '')[1]
157
158	def __init__(self, cipher=0, key=None, mac=0, mackey=None,
159	    crid=CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_HARDWARE):
160		self._ses = None
161		ses = SessionOp2()
162		ses.cipher = cipher
163		ses.mac = mac
164
165		if key is not None:
166			ses.keylen = len(key)
167			k = array.array('B', key)
168			ses.key = k.buffer_info()[0]
169		else:
170			self.key = None
171
172		if mackey is not None:
173			ses.mackeylen = len(mackey)
174			mk = array.array('B', mackey)
175			ses.mackey = mk.buffer_info()[0]
176			self._maclen = 16	# parameterize?
177		else:
178			self._maclen = None
179
180		if not cipher and not mac:
181			raise ValueError('one of cipher or mac MUST be specified.')
182		ses.crid = crid
183		#print `ses`
184		s = array.array('B', ses.pack_hdr())
185		#print `s`
186		ioctl(_cryptodev, CIOCGSESSION2, s, 1)
187		ses.unpack(s)
188
189		self._ses = ses.ses
190
191	def __del__(self):
192		if self._ses is None:
193			return
194
195		try:
196			ioctl(_cryptodev, CIOCFSESSION, _pack('I', self._ses))
197		except TypeError:
198			pass
199		self._ses = None
200
201	def _doop(self, op, src, iv):
202		cop = CryptOp()
203		cop.ses = self._ses
204		cop.op = op
205		cop.flags = 0
206		cop.len = len(src)
207		s = array.array('B', src)
208		cop.src = cop.dst = s.buffer_info()[0]
209		if self._maclen is not None:
210			m = array.array('B', [0] * self._maclen)
211			cop.mac = m.buffer_info()[0]
212		ivbuf = array.array('B', iv)
213		cop.iv = ivbuf.buffer_info()[0]
214
215		#print 'cop:', `cop`
216		ioctl(_cryptodev, CIOCCRYPT, str(cop))
217
218		s = s.tostring()
219		if self._maclen is not None:
220			return s, m.tostring()
221
222		return s
223
224	def _doaead(self, op, src, aad, iv, tag=None):
225		caead = CryptAEAD()
226		caead.ses = self._ses
227		caead.op = op
228		caead.flags = CRD_F_IV_EXPLICIT
229		caead.flags = 0
230		caead.len = len(src)
231		s = array.array('B', src)
232		caead.src = caead.dst = s.buffer_info()[0]
233		caead.aadlen = len(aad)
234		saad = array.array('B', aad)
235		caead.aad = saad.buffer_info()[0]
236
237		if self._maclen is None:
238			raise ValueError('must have a tag length')
239
240		if tag is None:
241			tag = array.array('B', [0] * self._maclen)
242		else:
243			assert len(tag) == self._maclen, `len(tag), self._maclen`
244			tag = array.array('B', tag)
245
246		caead.tag = tag.buffer_info()[0]
247
248		ivbuf = array.array('B', iv)
249		caead.ivlen = len(iv)
250		caead.iv = ivbuf.buffer_info()[0]
251
252		ioctl(_cryptodev, CIOCCRYPTAEAD, str(caead))
253
254		s = s.tostring()
255
256		return s, tag.tostring()
257
258	def perftest(self, op, size, timeo=3):
259		import random
260		import time
261
262		inp = array.array('B', (random.randint(0, 255) for x in xrange(size)))
263		out = array.array('B', inp)
264
265		# prep ioctl
266		cop = CryptOp()
267		cop.ses = self._ses
268		cop.op = op
269		cop.flags = 0
270		cop.len = len(inp)
271		s = array.array('B', inp)
272		cop.src = s.buffer_info()[0]
273		cop.dst = out.buffer_info()[0]
274		if self._maclen is not None:
275			m = array.array('B', [0] * self._maclen)
276			cop.mac = m.buffer_info()[0]
277		ivbuf = array.array('B', (random.randint(0, 255) for x in xrange(16)))
278		cop.iv = ivbuf.buffer_info()[0]
279
280		exit = [ False ]
281		def alarmhandle(a, b, exit=exit):
282			exit[0] = True
283
284		oldalarm = signal.signal(signal.SIGALRM, alarmhandle)
285		signal.alarm(timeo)
286
287		start = time.time()
288		reps = 0
289		while not exit[0]:
290			ioctl(_cryptodev, CIOCCRYPT, str(cop))
291			reps += 1
292
293		end = time.time()
294
295		signal.signal(signal.SIGALRM, oldalarm)
296
297		print 'time:', end - start
298		print 'perf MB/sec:', (reps * size) / (end - start) / 1024 / 1024
299
300	def encrypt(self, data, iv, aad=None):
301		if aad is None:
302			return self._doop(COP_ENCRYPT, data, iv)
303		else:
304			return self._doaead(COP_ENCRYPT, data, aad,
305			    iv)
306
307	def decrypt(self, data, iv, aad=None, tag=None):
308		if aad is None:
309			return self._doop(COP_DECRYPT, data, iv)
310		else:
311			return self._doaead(COP_DECRYPT, data, aad,
312			    iv, tag=tag)
313
314class MismatchError(Exception):
315	pass
316
317class KATParser:
318	def __init__(self, fname, fields):
319		self.fp = open(fname)
320		self.fields = set(fields)
321		self._pending = None
322
323	def __iter__(self):
324		while True:
325			didread = False
326			if self._pending is not None:
327				i = self._pending
328				self._pending = None
329			else:
330				i = self.fp.readline()
331				didread = True
332
333			if didread and not i:
334				return
335
336			if (i and i[0] == '#') or not i.strip():
337				continue
338			if i[0] == '[':
339				yield i[1:].split(']', 1)[0], self.fielditer()
340			else:
341				raise ValueError('unknown line: %s' % `i`)
342
343	def eatblanks(self):
344		while True:
345			line = self.fp.readline()
346			if line == '':
347				break
348
349			line = line.strip()
350			if line:
351				break
352
353		return line
354
355	def fielditer(self):
356		while True:
357			values = {}
358
359			line = self.eatblanks()
360			if not line or line[0] == '[':
361				self._pending = line
362				return
363
364			while True:
365				try:
366					f, v = line.split(' =')
367				except:
368					if line == 'FAIL':
369						f, v = 'FAIL', ''
370					else:
371						print 'line:', `line`
372						raise
373				v = v.strip()
374
375				if f in values:
376					raise ValueError('already present: %s' % `f`)
377				values[f] = v
378				line = self.fp.readline().strip()
379				if not line:
380					break
381
382			# we should have everything
383			remain = self.fields.copy() - set(values.keys())
384			# XXX - special case GCM decrypt
385			if remain and not ('FAIL' in values and 'PT' in remain):
386					raise ValueError('not all fields found: %s' % `remain`)
387
388			yield values
389
390def _spdechex(s):
391	return ''.join(s.split()).decode('hex')
392
393if __name__ == '__main__':
394	if True:
395		try:
396			crid = Crypto.findcrid('aesni0')
397			print 'aesni:', crid
398		except IOError:
399			print 'aesni0 not found'
400
401		for i in xrange(10):
402			try:
403				name = Crypto.getcridname(i)
404				print '%2d: %s' % (i, `name`)
405			except IOError:
406				pass
407	elif False:
408		kp = KATParser('/usr/home/jmg/aesni.testing/format tweak value input - data unit seq no/XTSGenAES128.rsp', [ 'COUNT', 'DataUnitLen', 'Key', 'DataUnitSeqNumber', 'PT', 'CT' ])
409		for mode, ni in kp:
410			print `i`, `ni`
411			for j in ni:
412				print `j`
413	elif False:
414		key = _spdechex('c939cc13397c1d37de6ae0e1cb7c423c')
415		iv = _spdechex('00000000000000000000000000000001')
416		pt = _spdechex('ab3cabed693a32946055524052afe3c9cb49664f09fc8b7da824d924006b7496353b8c1657c5dec564d8f38d7432e1de35aae9d95590e66278d4acce883e51abaf94977fcd3679660109a92bf7b2973ccd547f065ec6cee4cb4a72a5e9f45e615d920d76cb34cba482467b3e21422a7242e7d931330c0fbf465c3a3a46fae943029fd899626dda542750a1eee253df323c6ef1573f1c8c156613e2ea0a6cdbf2ae9701020be2d6a83ecb7f3f9d8e')
417		#pt = _spdechex('00000000000000000000000000000000')
418		ct = _spdechex('f42c33853ecc5ce2949865fdb83de3bff1089e9360c94f830baebfaff72836ab5236f77212f1e7396c8c54ac73d81986375a6e9e299cfeca5ba051ed25e8d1affa5beaf6c1d2b45e90802408f2ced21663497e906de5f29341e5e52ddfea5363d628b3eb7806835e17bae051b3a6da3f8e2941fe44384eac17a9d298d2c331ca8320c775b5d53263a5e905059d891b21dede2d8110fd427c7bd5a9a274ddb47b1945ee79522203b6e297d0e399ef')
419
420		c = Crypto(CRYPTO_AES_ICM, key)
421		enc = c.encrypt(pt, iv)
422
423		print 'enc:', enc.encode('hex')
424		print ' ct:', ct.encode('hex')
425
426		assert ct == enc
427
428		dec = c.decrypt(ct, iv)
429
430		print 'dec:', dec.encode('hex')
431		print ' pt:', pt.encode('hex')
432
433		assert pt == dec
434	elif False:
435		key = _spdechex('c939cc13397c1d37de6ae0e1cb7c423c')
436		iv = _spdechex('00000000000000000000000000000001')
437		pt = _spdechex('ab3cabed693a32946055524052afe3c9cb49664f09fc8b7da824d924006b7496353b8c1657c5dec564d8f38d7432e1de35aae9d95590e66278d4acce883e51abaf94977fcd3679660109a92bf7b2973ccd547f065ec6cee4cb4a72a5e9f45e615d920d76cb34cba482467b3e21422a7242e7d931330c0fbf465c3a3a46fae943029fd899626dda542750a1eee253df323c6ef1573f1c8c156613e2ea0a6cdbf2ae9701020be2d6a83ecb7f3f9d8e0a3f')
438		#pt = _spdechex('00000000000000000000000000000000')
439		ct = _spdechex('f42c33853ecc5ce2949865fdb83de3bff1089e9360c94f830baebfaff72836ab5236f77212f1e7396c8c54ac73d81986375a6e9e299cfeca5ba051ed25e8d1affa5beaf6c1d2b45e90802408f2ced21663497e906de5f29341e5e52ddfea5363d628b3eb7806835e17bae051b3a6da3f8e2941fe44384eac17a9d298d2c331ca8320c775b5d53263a5e905059d891b21dede2d8110fd427c7bd5a9a274ddb47b1945ee79522203b6e297d0e399ef3768')
440
441		c = Crypto(CRYPTO_AES_ICM, key)
442		enc = c.encrypt(pt, iv)
443
444		print 'enc:', enc.encode('hex')
445		print ' ct:', ct.encode('hex')
446
447		assert ct == enc
448
449		dec = c.decrypt(ct, iv)
450
451		print 'dec:', dec.encode('hex')
452		print ' pt:', pt.encode('hex')
453
454		assert pt == dec
455	elif False:
456		key = _spdechex('c939cc13397c1d37de6ae0e1cb7c423c')
457		iv = _spdechex('6eba2716ec0bd6fa5cdef5e6d3a795bc')
458		pt = _spdechex('ab3cabed693a32946055524052afe3c9cb49664f09fc8b7da824d924006b7496353b8c1657c5dec564d8f38d7432e1de35aae9d95590e66278d4acce883e51abaf94977fcd3679660109a92bf7b2973ccd547f065ec6cee4cb4a72a5e9f45e615d920d76cb34cba482467b3e21422a7242e7d931330c0fbf465c3a3a46fae943029fd899626dda542750a1eee253df323c6ef1573f1c8c156613e2ea0a6cdbf2ae9701020be2d6a83ecb7f3f9d8e0a3f')
459		ct = _spdechex('f1f81f12e72e992dbdc304032705dc75dc3e4180eff8ee4819906af6aee876d5b00b7c36d282a445ce3620327be481e8e53a8e5a8e5ca9abfeb2281be88d12ffa8f46d958d8224738c1f7eea48bda03edbf9adeb900985f4fa25648b406d13a886c25e70cfdecdde0ad0f2991420eb48a61c64fd797237cf2798c2675b9bb744360b0a3f329ac53bbceb4e3e7456e6514f1a9d2f06c236c31d0f080b79c15dce1096357416602520daa098b17d1af427')
460		c = Crypto(CRYPTO_AES_CBC, key)
461
462		enc = c.encrypt(pt, iv)
463
464		print 'enc:', enc.encode('hex')
465		print ' ct:', ct.encode('hex')
466
467		assert ct == enc
468
469		dec = c.decrypt(ct, iv)
470
471		print 'dec:', dec.encode('hex')
472		print ' pt:', pt.encode('hex')
473
474		assert pt == dec
475	elif False:
476		key = _spdechex('c939cc13397c1d37de6ae0e1cb7c423c')
477		iv = _spdechex('b3d8cc017cbb89b39e0f67e2')
478		pt = _spdechex('c3b3c41f113a31b73d9a5cd4321030')
479		aad = _spdechex('24825602bd12a984e0092d3e448eda5f')
480		ct = _spdechex('93fe7d9e9bfd10348a5606e5cafa7354')
481		ct = _spdechex('93fe7d9e9bfd10348a5606e5cafa73')
482		tag = _spdechex('0032a1dc85f1c9786925a2e71d8272dd')
483		tag = _spdechex('8d11a0929cb3fbe1fef01a4a38d5f8ea')
484
485		c = Crypto(CRYPTO_AES_NIST_GCM_16, key,
486		    mac=CRYPTO_AES_128_NIST_GMAC, mackey=key)
487
488		enc, enctag = c.encrypt(pt, iv, aad=aad)
489
490		print 'enc:', enc.encode('hex')
491		print ' ct:', ct.encode('hex')
492
493		assert enc == ct
494
495		print 'etg:', enctag.encode('hex')
496		print 'tag:', tag.encode('hex')
497		assert enctag == tag
498
499		# Make sure we get EBADMSG
500		#enctag = enctag[:-1] + 'a'
501		dec, dectag = c.decrypt(ct, iv, aad=aad, tag=enctag)
502
503		print 'dec:', dec.encode('hex')
504		print ' pt:', pt.encode('hex')
505
506		assert dec == pt
507
508		print 'dtg:', dectag.encode('hex')
509		print 'tag:', tag.encode('hex')
510
511		assert dectag == tag
512	elif False:
513		key = _spdechex('c939cc13397c1d37de6ae0e1cb7c423c')
514		iv = _spdechex('b3d8cc017cbb89b39e0f67e2')
515		key = key + iv[:4]
516		iv = iv[4:]
517		pt = _spdechex('c3b3c41f113a31b73d9a5cd432103069')
518		aad = _spdechex('24825602bd12a984e0092d3e448eda5f')
519		ct = _spdechex('93fe7d9e9bfd10348a5606e5cafa7354')
520		tag = _spdechex('0032a1dc85f1c9786925a2e71d8272dd')
521
522		c = Crypto(CRYPTO_AES_GCM_16, key, mac=CRYPTO_AES_128_GMAC, mackey=key)
523
524		enc, enctag = c.encrypt(pt, iv, aad=aad)
525
526		print 'enc:', enc.encode('hex')
527		print ' ct:', ct.encode('hex')
528
529		assert enc == ct
530
531		print 'etg:', enctag.encode('hex')
532		print 'tag:', tag.encode('hex')
533		assert enctag == tag
534	elif False:
535		for i in xrange(100000):
536			c = Crypto(CRYPTO_AES_XTS, '1bbfeadf539daedcae33ced497343f3ca1f2474ad932b903997d44707db41382'.decode('hex'))
537			data = '52a42bca4e9425a25bbc8c8bf6129dec'.decode('hex')
538			ct = '517e602becd066b65fa4f4f56ddfe240'.decode('hex')
539			iv = _pack('QQ', 71, 0)
540
541			enc = c.encrypt(data, iv)
542			assert enc == ct
543	elif True:
544		c = Crypto(CRYPTO_AES_XTS, '1bbfeadf539daedcae33ced497343f3ca1f2474ad932b903997d44707db41382'.decode('hex'))
545		data = '52a42bca4e9425a25bbc8c8bf6129dec'.decode('hex')
546		ct = '517e602becd066b65fa4f4f56ddfe240'.decode('hex')
547		iv = _pack('QQ', 71, 0)
548
549		enc = c.encrypt(data, iv)
550		assert enc == ct
551
552		dec = c.decrypt(enc, iv)
553		assert dec == data
554
555		#c.perftest(COP_ENCRYPT, 192*1024, reps=30000)
556
557	else:
558		key = '1bbfeadf539daedcae33ced497343f3ca1f2474ad932b903997d44707db41382'.decode('hex')
559		print 'XTS %d testing:' % (len(key) * 8)
560		c = Crypto(CRYPTO_AES_XTS, key)
561		for i in [ 8192, 192*1024]:
562			print 'block size: %d' % i
563			c.perftest(COP_ENCRYPT, i)
564			c.perftest(COP_DECRYPT, i)
565