xref: /freebsd/tests/sys/opencrypto/cryptotest.py (revision ef36b3f75658d201edb495068db5e1be49593de5)
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' ]
49desmodules = [ 'cryptosoft0', ]
50shamodules = [ 'cryptosoft0', 'aesni0', 'ccr0' ]
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					c = Crypto(cryptodev.CRYPTO_AES_NIST_GCM_16,
112					    cipherkey,
113					    mac=self._gmacsizes[len(cipherkey)],
114					    mackey=cipherkey, crid=crid)
115
116					if mode == 'ENCRYPT':
117						rct, rtag = c.encrypt(pt, iv, aad)
118						rtag = rtag[:len(tag)]
119						data['rct'] = rct.encode('hex')
120						data['rtag'] = rtag.encode('hex')
121						self.assertEqual(rct, ct, repr(data))
122						self.assertEqual(rtag, tag, repr(data))
123					else:
124						if len(tag) != 16:
125							continue
126						args = (ct, iv, aad, tag)
127						if 'FAIL' in data:
128							self.assertRaises(IOError,
129								c.decrypt, *args)
130						else:
131							rpt, rtag = c.decrypt(*args)
132							data['rpt'] = rpt.encode('hex')
133							data['rtag'] = rtag.encode('hex')
134							self.assertEqual(rpt, pt,
135							    repr(data))
136
137		def runCBC(self, fname):
138			curfun = None
139			for mode, lines in cryptodev.KATParser(fname,
140			    [ 'COUNT', 'KEY', 'IV', 'PLAINTEXT', 'CIPHERTEXT', ]):
141				if mode == 'ENCRYPT':
142					swapptct = False
143					curfun = Crypto.encrypt
144				elif mode == 'DECRYPT':
145					swapptct = True
146					curfun = Crypto.decrypt
147				else:
148					raise RuntimeError('unknown mode: %r' % repr(mode))
149
150				for data in lines:
151					curcnt = int(data['COUNT'])
152					cipherkey = data['KEY'].decode('hex')
153					iv = data['IV'].decode('hex')
154					pt = data['PLAINTEXT'].decode('hex')
155					ct = data['CIPHERTEXT'].decode('hex')
156
157					if swapptct:
158						pt, ct = ct, pt
159					# run the fun
160					c = Crypto(cryptodev.CRYPTO_AES_CBC, cipherkey, crid=crid)
161					r = curfun(c, pt, iv)
162					self.assertEqual(r, ct)
163
164		def runXTS(self, fname, meth):
165			curfun = None
166			for mode, lines in cryptodev.KATParser(fname,
167			    [ 'COUNT', 'DataUnitLen', 'Key', 'DataUnitSeqNumber', 'PT',
168			    'CT' ]):
169				if mode == 'ENCRYPT':
170					swapptct = False
171					curfun = Crypto.encrypt
172				elif mode == 'DECRYPT':
173					swapptct = True
174					curfun = Crypto.decrypt
175				else:
176					raise RuntimeError('unknown mode: %r' % repr(mode))
177
178				for data in lines:
179					curcnt = int(data['COUNT'])
180					nbits = int(data['DataUnitLen'])
181					cipherkey = data['Key'].decode('hex')
182					iv = struct.pack('QQ', int(data['DataUnitSeqNumber']), 0)
183					pt = data['PT'].decode('hex')
184					ct = data['CT'].decode('hex')
185
186					if nbits % 128 != 0:
187						# XXX - mark as skipped
188						continue
189					if swapptct:
190						pt, ct = ct, pt
191					# run the fun
192					c = Crypto(meth, cipherkey, crid=crid)
193					r = curfun(c, pt, iv)
194					self.assertEqual(r, ct)
195
196		###############
197		##### DES #####
198		###############
199		@unittest.skipIf(cname not in desmodules, 'skipping DES on %s' % (cname))
200		def test_tdes(self):
201			for i in katg('KAT_TDES', 'TCBC[a-z]*.rsp'):
202				self.runTDES(i)
203
204		def runTDES(self, fname):
205			curfun = None
206			for mode, lines in cryptodev.KATParser(fname,
207			    [ 'COUNT', 'KEYs', 'IV', 'PLAINTEXT', 'CIPHERTEXT', ]):
208				if mode == 'ENCRYPT':
209					swapptct = False
210					curfun = Crypto.encrypt
211				elif mode == 'DECRYPT':
212					swapptct = True
213					curfun = Crypto.decrypt
214				else:
215					raise RuntimeError('unknown mode: %r' % repr(mode))
216
217				for data in lines:
218					curcnt = int(data['COUNT'])
219					key = data['KEYs'] * 3
220					cipherkey = key.decode('hex')
221					iv = data['IV'].decode('hex')
222					pt = data['PLAINTEXT'].decode('hex')
223					ct = data['CIPHERTEXT'].decode('hex')
224
225					if swapptct:
226						pt, ct = ct, pt
227					# run the fun
228					c = Crypto(cryptodev.CRYPTO_3DES_CBC, cipherkey, crid=crid)
229					r = curfun(c, pt, iv)
230					self.assertEqual(r, ct)
231
232		###############
233		##### SHA #####
234		###############
235		@unittest.skipIf(cname not in shamodules, 'skipping SHA on %s' % str(cname))
236		def test_sha(self):
237			# SHA not available in software
238			pass
239			#for i in iglob('SHA1*'):
240			#	self.runSHA(i)
241
242		@unittest.skipIf(cname not in shamodules, 'skipping SHA on %s' % str(cname))
243		def test_sha1hmac(self):
244			for i in katg('hmactestvectors', 'HMAC.rsp'):
245				self.runSHA1HMAC(i)
246
247		def runSHA1HMAC(self, fname):
248			for hashlength, lines in cryptodev.KATParser(fname,
249			    [ 'Count', 'Klen', 'Tlen', 'Key', 'Msg', 'Mac' ]):
250				# E.g., hashlength will be "L=20" (bytes)
251				hashlen = int(hashlength.split("=")[1])
252
253				blocksize = None
254				if hashlen == 20:
255					alg = cryptodev.CRYPTO_SHA1_HMAC
256					blocksize = 64
257				elif hashlen == 28:
258					# Cryptodev doesn't support SHA-224
259					# Slurp remaining input in section
260					for data in lines:
261						continue
262					continue
263				elif hashlen == 32:
264					alg = cryptodev.CRYPTO_SHA2_256_HMAC
265					blocksize = 64
266				elif hashlen == 48:
267					alg = cryptodev.CRYPTO_SHA2_384_HMAC
268					blocksize = 128
269				elif hashlen == 64:
270					alg = cryptodev.CRYPTO_SHA2_512_HMAC
271					blocksize = 128
272				else:
273					# Skip unsupported hashes
274					# Slurp remaining input in section
275					for data in lines:
276						continue
277					continue
278
279				for data in lines:
280					key = data['Key'].decode('hex')
281					msg = data['Msg'].decode('hex')
282					mac = data['Mac'].decode('hex')
283					tlen = int(data['Tlen'])
284
285					if len(key) > blocksize:
286						continue
287
288					try:
289						c = Crypto(mac=alg, mackey=key,
290						    crid=crid)
291					except EnvironmentError, e:
292						# Can't test hashes the driver does not support.
293						if e.errno != errno.EOPNOTSUPP:
294							raise
295						continue
296
297					_, r = c.encrypt(msg, iv="")
298
299					# A limitation in cryptodev.py means we
300					# can only store MACs up to 16 bytes.
301					# That's good enough to validate the
302					# correct behavior, more or less.
303					maclen = min(tlen, 16)
304					self.assertEqual(r[:maclen], mac[:maclen], "Actual: " + \
305					    repr(r[:maclen].encode("hex")) + " Expected: " + repr(data))
306
307	return GendCryptoTestCase
308
309cryptosoft = GenTestCase('cryptosoft0')
310aesni = GenTestCase('aesni0')
311ccr = GenTestCase('ccr0')
312
313if __name__ == '__main__':
314	unittest.main()
315