xref: /freebsd/tests/sys/opencrypto/cryptotest.py (revision e796cc77c586c2955b2f3940dbf4991b31e8d289)
1#!/usr/bin/env python
2#
3# Copyright (c) 2014 The FreeBSD Foundation
4# All rights reserved.
5#
6# This software was developed by John-Mark Gurney under
7# the sponsorship from the FreeBSD Foundation.
8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions
10# are met:
11# 1.  Redistributions of source code must retain the above copyright
12#     notice, this list of conditions and the following disclaimer.
13# 2.  Redistributions in binary form must reproduce the above copyright
14#     notice, this list of conditions and the following disclaimer in the
15#     documentation and/or other materials provided with the distribution.
16#
17# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27# SUCH DAMAGE.
28#
29# $FreeBSD$
30#
31
32from __future__ import print_function
33import errno
34import cryptodev
35import itertools
36import os
37import struct
38import unittest
39from cryptodev import *
40from glob import iglob
41
42katdir = '/usr/local/share/nist-kat'
43
44def katg(base, glob):
45	assert os.path.exists(os.path.join(katdir, base)), "Please 'pkg install nist-kat'"
46	return iglob(os.path.join(katdir, base, glob))
47
48aesmodules = [ 'cryptosoft0', 'aesni0', 'ccr0', 'ccp0' ]
49desmodules = [ 'cryptosoft0', ]
50shamodules = [ 'cryptosoft0', 'aesni0', 'ccr0', 'ccp0' ]
51
52def GenTestCase(cname):
53	try:
54		crid = cryptodev.Crypto.findcrid(cname)
55	except IOError:
56		return None
57
58	class GendCryptoTestCase(unittest.TestCase):
59		###############
60		##### AES #####
61		###############
62		@unittest.skipIf(cname not in aesmodules, 'skipping AES on %s' % (cname))
63		def test_xts(self):
64			for i in katg('XTSTestVectors/format tweak value input - data unit seq no', '*.rsp'):
65				self.runXTS(i, cryptodev.CRYPTO_AES_XTS)
66
67		@unittest.skipIf(cname not in aesmodules, 'skipping AES on %s' % (cname))
68		def test_cbc(self):
69			for i in katg('KAT_AES', 'CBC[GKV]*.rsp'):
70				self.runCBC(i)
71
72		@unittest.skipIf(cname not in aesmodules, 'skipping AES on %s' % (cname))
73		def test_gcm(self):
74			for i in katg('gcmtestvectors', 'gcmEncrypt*'):
75				self.runGCM(i, 'ENCRYPT')
76
77			for i in katg('gcmtestvectors', 'gcmDecrypt*'):
78				self.runGCM(i, 'DECRYPT')
79
80		_gmacsizes = { 32: cryptodev.CRYPTO_AES_256_NIST_GMAC,
81			24: cryptodev.CRYPTO_AES_192_NIST_GMAC,
82			16: cryptodev.CRYPTO_AES_128_NIST_GMAC,
83		}
84		def runGCM(self, fname, mode):
85			curfun = None
86			if mode == 'ENCRYPT':
87				swapptct = False
88				curfun = Crypto.encrypt
89			elif mode == 'DECRYPT':
90				swapptct = True
91				curfun = Crypto.decrypt
92			else:
93				raise RuntimeError('unknown mode: %r' % repr(mode))
94
95			for bogusmode, lines in cryptodev.KATParser(fname,
96			    [ 'Count', 'Key', 'IV', 'CT', 'AAD', 'Tag', 'PT', ]):
97				for data in lines:
98					curcnt = int(data['Count'])
99					cipherkey = data['Key'].decode('hex')
100					iv = data['IV'].decode('hex')
101					aad = data['AAD'].decode('hex')
102					tag = data['Tag'].decode('hex')
103					if 'FAIL' not in data:
104						pt = data['PT'].decode('hex')
105					ct = data['CT'].decode('hex')
106
107					if len(iv) != 12:
108						# XXX - isn't supported
109						continue
110
111					try:
112						c = Crypto(cryptodev.CRYPTO_AES_NIST_GCM_16,
113						    cipherkey,
114						    mac=self._gmacsizes[len(cipherkey)],
115						    mackey=cipherkey, crid=crid)
116					except EnvironmentError, e:
117						# Can't test algorithms the driver does not support.
118						if e.errno != errno.EOPNOTSUPP:
119							raise
120						continue
121
122					if mode == 'ENCRYPT':
123						try:
124							rct, rtag = c.encrypt(pt, iv, aad)
125						except EnvironmentError, e:
126							# Can't test inputs the driver does not support.
127							if e.errno != errno.EINVAL:
128								raise
129							continue
130						rtag = rtag[:len(tag)]
131						data['rct'] = rct.encode('hex')
132						data['rtag'] = rtag.encode('hex')
133						self.assertEqual(rct, ct, repr(data))
134						self.assertEqual(rtag, tag, repr(data))
135					else:
136						if len(tag) != 16:
137							continue
138						args = (ct, iv, aad, tag)
139						if 'FAIL' in data:
140							self.assertRaises(IOError,
141								c.decrypt, *args)
142						else:
143							try:
144								rpt, rtag = c.decrypt(*args)
145							except EnvironmentError, e:
146								# Can't test inputs the driver does not support.
147								if e.errno != errno.EINVAL:
148									raise
149								continue
150							data['rpt'] = rpt.encode('hex')
151							data['rtag'] = rtag.encode('hex')
152							self.assertEqual(rpt, pt,
153							    repr(data))
154
155		def runCBC(self, fname):
156			curfun = None
157			for mode, lines in cryptodev.KATParser(fname,
158			    [ 'COUNT', 'KEY', 'IV', 'PLAINTEXT', 'CIPHERTEXT', ]):
159				if mode == 'ENCRYPT':
160					swapptct = False
161					curfun = Crypto.encrypt
162				elif mode == 'DECRYPT':
163					swapptct = True
164					curfun = Crypto.decrypt
165				else:
166					raise RuntimeError('unknown mode: %r' % repr(mode))
167
168				for data in lines:
169					curcnt = int(data['COUNT'])
170					cipherkey = data['KEY'].decode('hex')
171					iv = data['IV'].decode('hex')
172					pt = data['PLAINTEXT'].decode('hex')
173					ct = data['CIPHERTEXT'].decode('hex')
174
175					if swapptct:
176						pt, ct = ct, pt
177					# run the fun
178					c = Crypto(cryptodev.CRYPTO_AES_CBC, cipherkey, crid=crid)
179					r = curfun(c, pt, iv)
180					self.assertEqual(r, ct)
181
182		def runXTS(self, fname, meth):
183			curfun = None
184			for mode, lines in cryptodev.KATParser(fname,
185			    [ 'COUNT', 'DataUnitLen', 'Key', 'DataUnitSeqNumber', 'PT',
186			    'CT' ]):
187				if mode == 'ENCRYPT':
188					swapptct = False
189					curfun = Crypto.encrypt
190				elif mode == 'DECRYPT':
191					swapptct = True
192					curfun = Crypto.decrypt
193				else:
194					raise RuntimeError('unknown mode: %r' % repr(mode))
195
196				for data in lines:
197					curcnt = int(data['COUNT'])
198					nbits = int(data['DataUnitLen'])
199					cipherkey = data['Key'].decode('hex')
200					iv = struct.pack('QQ', int(data['DataUnitSeqNumber']), 0)
201					pt = data['PT'].decode('hex')
202					ct = data['CT'].decode('hex')
203
204					if nbits % 128 != 0:
205						# XXX - mark as skipped
206						continue
207					if swapptct:
208						pt, ct = ct, pt
209					# run the fun
210					try:
211						c = Crypto(meth, cipherkey, crid=crid)
212						r = curfun(c, pt, iv)
213					except EnvironmentError, e:
214						# Can't test hashes the driver does not support.
215						if e.errno != errno.EOPNOTSUPP:
216							raise
217						continue
218					self.assertEqual(r, ct)
219
220		###############
221		##### DES #####
222		###############
223		@unittest.skipIf(cname not in desmodules, 'skipping DES on %s' % (cname))
224		def test_tdes(self):
225			for i in katg('KAT_TDES', 'TCBC[a-z]*.rsp'):
226				self.runTDES(i)
227
228		def runTDES(self, fname):
229			curfun = None
230			for mode, lines in cryptodev.KATParser(fname,
231			    [ 'COUNT', 'KEYs', 'IV', 'PLAINTEXT', 'CIPHERTEXT', ]):
232				if mode == 'ENCRYPT':
233					swapptct = False
234					curfun = Crypto.encrypt
235				elif mode == 'DECRYPT':
236					swapptct = True
237					curfun = Crypto.decrypt
238				else:
239					raise RuntimeError('unknown mode: %r' % repr(mode))
240
241				for data in lines:
242					curcnt = int(data['COUNT'])
243					key = data['KEYs'] * 3
244					cipherkey = key.decode('hex')
245					iv = data['IV'].decode('hex')
246					pt = data['PLAINTEXT'].decode('hex')
247					ct = data['CIPHERTEXT'].decode('hex')
248
249					if swapptct:
250						pt, ct = ct, pt
251					# run the fun
252					c = Crypto(cryptodev.CRYPTO_3DES_CBC, cipherkey, crid=crid)
253					r = curfun(c, pt, iv)
254					self.assertEqual(r, ct)
255
256		###############
257		##### SHA #####
258		###############
259		@unittest.skipIf(cname not in shamodules, 'skipping SHA on %s' % str(cname))
260		def test_sha(self):
261			# SHA not available in software
262			pass
263			#for i in iglob('SHA1*'):
264			#	self.runSHA(i)
265
266		@unittest.skipIf(cname not in shamodules, 'skipping SHA on %s' % str(cname))
267		def test_sha1hmac(self):
268			for i in katg('hmactestvectors', 'HMAC.rsp'):
269				self.runSHA1HMAC(i)
270
271		def runSHA1HMAC(self, fname):
272			for hashlength, lines in cryptodev.KATParser(fname,
273			    [ 'Count', 'Klen', 'Tlen', 'Key', 'Msg', 'Mac' ]):
274				# E.g., hashlength will be "L=20" (bytes)
275				hashlen = int(hashlength.split("=")[1])
276
277				blocksize = None
278				if hashlen == 20:
279					alg = cryptodev.CRYPTO_SHA1_HMAC
280					blocksize = 64
281				elif hashlen == 28:
282					# Cryptodev doesn't support SHA-224
283					# Slurp remaining input in section
284					for data in lines:
285						continue
286					continue
287				elif hashlen == 32:
288					alg = cryptodev.CRYPTO_SHA2_256_HMAC
289					blocksize = 64
290				elif hashlen == 48:
291					alg = cryptodev.CRYPTO_SHA2_384_HMAC
292					blocksize = 128
293				elif hashlen == 64:
294					alg = cryptodev.CRYPTO_SHA2_512_HMAC
295					blocksize = 128
296				else:
297					# Skip unsupported hashes
298					# Slurp remaining input in section
299					for data in lines:
300						continue
301					continue
302
303				for data in lines:
304					key = data['Key'].decode('hex')
305					msg = data['Msg'].decode('hex')
306					mac = data['Mac'].decode('hex')
307					tlen = int(data['Tlen'])
308
309					if len(key) > blocksize:
310						continue
311
312					try:
313						c = Crypto(mac=alg, mackey=key,
314						    crid=crid)
315					except EnvironmentError, e:
316						# Can't test hashes the driver does not support.
317						if e.errno != errno.EOPNOTSUPP:
318							raise
319						continue
320
321					_, r = c.encrypt(msg, iv="")
322
323					# A limitation in cryptodev.py means we
324					# can only store MACs up to 16 bytes.
325					# That's good enough to validate the
326					# correct behavior, more or less.
327					maclen = min(tlen, 16)
328					self.assertEqual(r[:maclen], mac[:maclen], "Actual: " + \
329					    repr(r[:maclen].encode("hex")) + " Expected: " + repr(data))
330
331	return GendCryptoTestCase
332
333cryptosoft = GenTestCase('cryptosoft0')
334aesni = GenTestCase('aesni0')
335ccr = GenTestCase('ccr0')
336ccp = GenTestCase('ccp0')
337
338if __name__ == '__main__':
339	unittest.main()
340