Home | History | Annotate | Download | only in NIST-CAVP
      1 #!/usr/bin/python
      2 #
      3 # Program that converts a NIST test vector RSP file to a C header
      4 # file.  Currently only tested with the ExtendedIV GCM test vectors.
      5 #
      6 import argparse
      7 import itertools
      8 
      9 
     10 def _parse_args():
     11   parser = argparse.ArgumentParser()
     12   parser.add_argument("-i", "--in", dest="input_files",
     13                       help="Comma separated list of input RSP files",
     14                       metavar="FILE.rsp", required=True)
     15   parser.add_argument("-o", "--out", dest="output_file",
     16                       help="Output C header file", metavar="FILE.h",
     17                       required=True)
     18   return parser.parse_args()
     19 
     20 
     21 def _read_nist_header(lines):
     22   """Parse a HEADER block, of form:
     23   [A = NUM1]
     24   [B = NUM2]
     25   \n
     26   """
     27   header = None  # {KEY1: INT1, KEY2: INT2, ...}
     28   while lines:
     29     line = lines.pop(0)
     30     if not line:
     31       return header
     32     if line.startswith('['):
     33       if not header:
     34         header = {}
     35       key, value = line.strip('][').split('=')
     36       header[key.strip()] = int(value.strip())
     37     else:
     38       raise Exception('Invalid header block line: %s' % line)
     39   return header
     40 
     41 
     42 def _read_nist_blocks(lines):
     43   """Parse a DATA block, of form:
     44   A = HEX1
     45   B = HEX2
     46   \n
     47   """
     48   blocks = []  # [{KEY1: HEXSTR1, KEY2: HEXSTR2, ...}
     49   while lines:
     50     line = lines[0]
     51     if not line:
     52       # Block not started, ignore blank line.
     53       lines.pop(0)
     54       continue
     55     if line.startswith('[') or line.startswith('#'):
     56       # Next header encountered.
     57       break
     58     # Read a block.
     59     block = {}
     60     while lines:
     61       line = lines.pop(0)
     62       if not line:
     63         # End of block.
     64         break
     65       if '=' not in line:
     66         raise Exception('Unexpected line: %s' % line)
     67       key, value = line.split('=')
     68       block[key.strip()] = value.strip()
     69     blocks.append(block)
     70   return blocks
     71 
     72 
     73 def _load_nist(infile):
     74   lines = infile.readlines()
     75   lines = [l.strip() for l in lines]
     76   data = []   # [(header1: [block1, block2]), (header2: ...)]
     77   mode = lines[1].split(' ')[1]
     78 
     79   while lines:
     80     line = lines[0]
     81     if not line or line.startswith('#'):
     82       # Ignore blank lines or comments.
     83       lines.pop(0)
     84       continue
     85     if not line.startswith('['):
     86       raise Exception('Header not found: %s' % line)
     87     header = _read_nist_header(lines)
     88     blocks = _read_nist_blocks(lines)
     89     data.append((header, blocks))
     90   return mode, data
     91 
     92 
     93 def _words32(v):
     94   # Split hex string into 32-bit words.
     95   args = [iter(v)] * 8
     96   words = [''.join(b) for b in itertools.izip_longest(fillvalue='', *args)]
     97   return map(lambda w: w.ljust(8, '0'), words)    # Zero-pad the last word.
     98 
     99 
    100 def _bswap32(w):
    101   if len(w) != 8:
    102     raise Exception('Expected 32-bit input word, got: %s' % w)
    103   w = w[::-1]   # Reverse hex string.
    104   w = iter(w)
    105   return ''.join([b2 + b1 for b1, b2 in itertools.izip(w, w)])
    106 
    107 
    108 def _format32(block):
    109   # Format values into 32-bit words, with appropriate endienness.
    110   b = {}
    111   for k, v in block.iteritems():
    112     if k == 'Count':
    113       b[k] = v
    114       continue
    115     if not v:    # Strip keys with empty values.
    116       continue
    117     v = ', '.join(['0x%s' % _bswap32(w) for w in _words32(v)])
    118     b[k] = '{%s}' % v
    119   return b
    120 
    121 
    122 def _write_header(input_files, outfile, mode, data):
    123   outfile.write('/*\n * Auto generated by nist2h.py from input files:\n')
    124   for fname in input_files.split(','):
    125     outfile.write(' *     %s\n' % fname)
    126   outfile.write(' */\n')
    127   outfile.write('#ifndef AES_%s_CAVP_H\n' % mode)
    128   outfile.write('#define AES_%s_CAVP_H\n' % mode)
    129   outfile.write('''
    130 typedef struct {
    131     uint32_t key[8];
    132     uint32_t key_len;
    133     uint32_t IV[128];
    134     uint32_t IV_len;
    135     uint32_t PT[64];
    136     uint32_t PT_len;
    137     uint32_t CT[64];
    138     uint32_t AAD[96];
    139     uint32_t AAD_len;
    140     uint32_t tag[16];
    141     uint32_t tag_len;
    142 } %s_data;
    143 
    144 ''' % mode.lower())
    145   outfile.write('const %s_data NIST_%s_DATA[] = {\n' % (mode.lower(), mode))
    146   for i, entry in enumerate(data):
    147     header, blocks = entry
    148     for block in blocks:
    149       block = _format32(block)
    150       AAD = '{}'
    151       AAD_len = 0
    152       if 'AAD' in block:
    153         AAD = block['AAD']
    154         AAD_len = header['AADlen']
    155       PT = '{}'
    156       PT_len = 0
    157       if 'PT' in block:
    158         PT = block['PT']
    159         PT_len = header['PTlen']
    160       CT = '{}'
    161       if 'CT' in block:
    162         CT = block['CT']
    163 
    164       line = ('{key}, {key_len}, {IV}, {IV_len}, {PT}, {PT_len}, '
    165               '{CT}, {AAD}, {AAD_len}, {tag}, {tag_len}').format(
    166                   key=block['Key'], key_len=header['Keylen'], IV=block['IV'],
    167                   IV_len=header['IVlen'], PT=PT, PT_len=PT_len, CT=CT, AAD=AAD,
    168                   AAD_len=AAD_len, tag=block['Tag'], tag_len=header['Taglen'])
    169       outfile.write('    {%s},\n' % line)
    170   outfile.write('};\n\n')
    171   outfile.write('#endif /* ! AES_TESTS_%s_DATA_H */\n' % mode)
    172 
    173 
    174 if __name__ == '__main__':
    175   args = _parse_args()
    176   data = []
    177   for input_file in args.input_files.split(','):
    178     print 'Processing:', input_file
    179     with open(input_file) as f:
    180       mode, loaded_data = _load_nist(f)
    181       data.extend(loaded_data)
    182   with open(args.output_file, 'w+') as f:
    183     _write_header(args.input_files, f, mode, data)
    184