crypto: Test all of the AES-CCM KAT vectors.

Previously, only test vectors which used the default nonce and tag
sizes (12 and 16, respectively) were tested.  This now tests all of
the vectors.  This exposed some additional issues around requests with
an empty payload (which wasn't supported) and an empty AAD (which
falls back to CIOCCRYPT instead of CIOCCRYPTAEAD).

- Make use of the 'ivlen' and 'maclen' fields for CIOGSESSION2 to
  test AES-CCM vectors with non-default nonce and tag lengths.

- Permit requests with an empty payload.

- Permit an input MAC for requests without AAD.

Reviewed by:	markj
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D32121

(cherry picked from commit 668770dc7d)
This commit is contained in:
John Baldwin 2021-10-06 14:08:48 -07:00
parent 9039a0fae4
commit dffa7f13db
2 changed files with 56 additions and 39 deletions

View file

@ -79,10 +79,10 @@ class SessionOp2(dpkt.Packet):
('mackey', 'P', 0),
('ses', 'I', 0),
('crid', 'i', 0),
('ivlen', 'i', 0),
('maclen', 'i', 0),
('pad0', 'i', 0),
('pad1', 'i', 0),
('pad2', 'i', 0),
('pad3', 'i', 0),
)
class CryptOp(dpkt.Packet):
@ -159,6 +159,11 @@ def array_tobytes(array_obj):
return array_obj.tobytes()
return array_obj.tostring()
def empty_bytes():
if sys.version_info[0] >= 3:
return b''
return ""
class Crypto:
@staticmethod
def findcrid(name):
@ -169,7 +174,8 @@ class Crypto:
return _findop(crid, '')[1]
def __init__(self, cipher=0, key=None, mac=0, mackey=None,
crid=CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_HARDWARE, maclen=None):
crid=CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_HARDWARE, maclen=None,
ivlen=None):
self._ses = None
self._maclen = maclen
ses = SessionOp2()
@ -191,6 +197,10 @@ class Crypto:
if not cipher and not mac:
raise ValueError('one of cipher or mac MUST be specified.')
ses.crid = crid
if ivlen:
ses.ivlen = ivlen
if maclen:
ses.maclen = maclen
#print(ses)
s = array.array('B', ses.pack_hdr())
#print(s)
@ -209,16 +219,23 @@ class Crypto:
pass
self._ses = None
def _doop(self, op, src, iv):
def _doop(self, op, src, iv, mac=None):
cop = CryptOp()
cop.ses = self._ses
cop.op = op
cop.flags = 0
cop.len = len(src)
s = array.array('B', src)
cop.src = cop.dst = s.buffer_info()[0]
if src is not None:
cop.len = len(src)
s = array.array('B', src)
cop.src = cop.dst = s.buffer_info()[0]
if mac is not None:
assert len(mac) == self._maclen, \
'%d != %d' % (len(tag), self._maclen)
if self._maclen is not None:
m = array.array('B', [0] * self._maclen)
if mac is None:
m = array.array('B', [0] * self._maclen)
else:
m = array.array('B', mac)
cop.mac = m.buffer_info()[0]
ivbuf = array.array('B', str_to_ascii(iv))
cop.iv = ivbuf.buffer_info()[0]
@ -226,7 +243,10 @@ class Crypto:
#print('cop:', cop)
ioctl(_cryptodev, CIOCCRYPT, bytes(cop))
s = array_tobytes(s)
if src is not None:
s = array_tobytes(s)
else:
s = empty_bytes()
if self._maclen is not None:
return s, array_tobytes(m)
@ -238,10 +258,11 @@ class Crypto:
caead.op = op
caead.flags = CRD_F_IV_EXPLICIT
caead.flags = 0
src = str_to_ascii(src)
caead.len = len(src)
s = array.array('B', src)
caead.src = caead.dst = s.buffer_info()[0]
if src is not None and len(src) != 0:
src = str_to_ascii(src)
caead.len = len(src)
s = array.array('B', src)
caead.src = caead.dst = s.buffer_info()[0]
aad = str_to_ascii(aad)
caead.aadlen = len(aad)
saad = array.array('B', aad)
@ -266,7 +287,10 @@ class Crypto:
ioctl(_cryptodev, CIOCCRYPTAEAD, bytes(caead))
s = array_tobytes(s)
if src is not None:
s = array_tobytes(s)
else:
s = empty_bytes()
return s, array_tobytes(tag)
@ -320,7 +344,7 @@ class Crypto:
def decrypt(self, data, iv, aad=None, tag=None):
if aad is None:
return self._doop(COP_DECRYPT, data, iv)
return self._doop(COP_DECRYPT, data, iv, mac=tag)
else:
return self._doaead(COP_DECRYPT, data, aad,
iv, tag=tag)

View file

@ -243,25 +243,26 @@ def GenTestCase(cname):
def runCCMEncryptWithParser(self, parser):
for data in next(parser):
Nlen = int(data['Nlen'])
if Nlen != 12:
# OCF only supports 12 byte IVs
continue
Tlen = int(data['Tlen'])
key = binascii.unhexlify(data['Key'])
nonce = binascii.unhexlify(data['Nonce'])
Alen = int(data['Alen'])
Plen = int(data['Plen'])
if Alen != 0:
aad = binascii.unhexlify(data['Adata'])
else:
aad = None
payload = binascii.unhexlify(data['Payload'])
if Plen != 0:
payload = binascii.unhexlify(data['Payload'])
else:
payload = None
ct = binascii.unhexlify(data['CT'])
try:
c = Crypto(crid=crid,
cipher=cryptodev.CRYPTO_AES_CCM_16,
key=key,
mac=cryptodev.CRYPTO_AES_CCM_CBC_MAC,
mackey=key, maclen=16)
mackey=key, maclen=Tlen, ivlen=Nlen)
r, tag = Crypto.encrypt(c, payload,
nonce, aad)
except EnvironmentError as e:
@ -280,36 +281,29 @@ def GenTestCase(cname):
self.runCCMDecryptWithParser(parser)
def runCCMDecryptWithParser(self, parser):
# XXX: Note that all of the current CCM
# decryption test vectors use IV and tag sizes
# that aren't supported by OCF none of the
# tests are actually ran.
for data in next(parser):
Nlen = int(data['Nlen'])
if Nlen != 12:
# OCF only supports 12 byte IVs
continue
Tlen = int(data['Tlen'])
if Tlen != 16:
# OCF only supports 16 byte tags
continue
key = binascii.unhexlify(data['Key'])
nonce = binascii.unhexlify(data['Nonce'])
Alen = int(data['Alen'])
Plen = int(data['Plen'])
if Alen != 0:
aad = binascii.unhexlify(data['Adata'])
else:
aad = None
ct = binascii.unhexlify(data['CT'])
tag = ct[-16:]
ct = ct[:-16]
tag = ct[-Tlen:]
if Plen != 0:
payload = ct[:-Tlen]
else:
payload = None
try:
c = Crypto(crid=crid,
cipher=cryptodev.CRYPTO_AES_CCM_16,
key=key,
mac=cryptodev.CRYPTO_AES_CCM_CBC_MAC,
mackey=key, maclen=16)
mackey=key, maclen=Tlen, ivlen=Nlen)
except EnvironmentError as e:
if e.errno != errno.EOPNOTSUPP:
raise
@ -319,12 +313,11 @@ def GenTestCase(cname):
self.assertRaises(IOError,
c.decrypt, payload, nonce, aad, tag)
else:
r = Crypto.decrypt(c, payload, nonce,
aad, tag)
r, tag = Crypto.decrypt(c, payload, nonce,
aad, tag)
payload = binascii.unhexlify(data['Payload'])
plen = int(data('Plen'))
payload = payload[:plen]
payload = payload[:Plen]
self.assertEqual(r, payload,
"Count " + data['Count'] + \
" Actual: " + repr(binascii.hexlify(r)) + \