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# 6import argparse 7import itertools 8 9 10def _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 21def _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 42def _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 73def _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 93def _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 100def _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 108def _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 122def _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(''' 130typedef 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 174if __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