1#! /usr/bin/python 2# This file and its contents are supplied under the terms of the 3# Common Development and Distribution License ("CDDL"), version 1.0. 4# You may only use this file in accordance with the terms of version 5# 1.0 of the CDDL. 6# 7# A full copy of the text of the CDDL should have accompanied this 8# source. A copy of the CDDL is also available via the Internet at 9# http://www.illumos.org/license/CDDL. 10# 11 12# 13# Copyright 2023 RackTop Systems, Inc. 14# 15 16# 17# Convert an NIST GCMVS test vector file into arrays for crypto-test. 18# Filters for 'plaintextlen' == 0, and uses 'AAD' for DATA arrays, 19# so that the result is a GMAC test. 20# 21 22from dataclasses import dataclass, field 23import argparse 24 25@dataclass(frozen=True) 26class TestVector: 27 """Class tracking individual vectors""" 28 29 key: list 30 iv: list 31 pt: list 32 aad: list 33 ct: list 34 tag: list 35 36@dataclass 37class TestSet: 38 """Class tracking vectors with particular parameters""" 39 40 keylen: int 41 ivlen: int 42 ptlen: int 43 aadlen: int 44 taglen: int 45 ctlen: int = field(init=False) 46 vectors: list = field(default_factory=list) 47 48 def __post_init__(self): 49 self.ctlen = self.ptlen + self.taglen 50 51 def add_case(self, key, iv, pt, aad, ct, tag): 52 if len(key) * 8 != self.keylen: 53 raise ValueError(f'Key \'{" ".join(key)}\' is length {len(key) * 8} but expected length {self.keylen}') 54 if len(iv) * 8 != self.ivlen: 55 raise ValueError(f'Iv \'{" ".join(iv)}\' is length {len(iv) * 8} but expected length {self.ivlen}') 56 if len(pt) * 8 != self.ptlen: 57 raise ValueError(f'Pt \'{" ".join(pt)}\' is length {len(pt) * 8} but expected length {self.ptlen}') 58 if len(aad) * 8 != self.aadlen: 59 raise ValueError(f'Aad \'{" ".join(aad)}\' is length {len(aad) * 8} but expected length {self.aadlen}') 60 if len(ct) * 8 != self.ptlen: 61 raise ValueError(f'Ct \'{" ".join(ct)}\' is length {len(ct) * 8} but expected length {self.ptlen}') 62 if len(tag) * 8 != self.taglen: 63 raise ValueError(f'Tag \'{" ".join(tag)}\' is length {len(tag) * 8} but expected length {self.taglen}') 64 if self.ptlen == 0 and self.taglen == 128 and self.ivlen == 96: 65 #For Decrypt, the tag needs to be part of the ciphertext 66 self.vectors.append(TestVector(key, iv, pt, aad, ct + tag, [])) 67 68 def __iter__(self): 69 return iter(self.vectors) 70 71 def __len__(self): 72 return len(self.vectors) 73 74def genhex(string): 75 off = 0 76 strlen = len(string) 77 if strlen % 2 != 0: 78 yield '0x0' + string[0] 79 off += 1 80 81 while off < strlen: 82 yield '0x' + string[off:off+2] 83 off += 2 84 85def main(): 86 parser = argparse.ArgumentParser(description= 87 'Convert an NIST GCM Test Vector File to a C header on stdout') 88 89 parser.add_argument('input_file', 90 type=argparse.FileType('r'), 91 help='The NIST .rsp file containing test vectors') 92 args = parser.parse_args() 93 94 input_file = args.input_file 95 96 tests = [] 97 tset = None 98 for line in input_file: 99 val = line.strip('[]\n').split(' = ') 100 if not val[0] and tset != None: 101 tset.add_case(key, iv, pt, aad, ct, tag) 102 103 if val[0] == 'Keylen': 104 keylen = int(val[1]) 105 tset = None 106 elif val[0] == 'IVlen': 107 ivlen = int(val[1]) 108 elif val[0] == 'PTlen': 109 ptlen = int(val[1]) 110 elif val[0] == 'AADlen': 111 aadlen = int(val[1]) 112 elif val[0] == 'Taglen': 113 taglen = int(val[1]) 114 elif val[0] == 'Count': 115 if val[1] == '0': 116 tset = TestSet(keylen, ivlen, ptlen, aadlen, taglen) 117 tests.append(tset) 118 elif val[0] == 'Key': 119 key = list(genhex(val[1])) 120 elif val[0] == 'IV': 121 iv = list(genhex(val[1])) 122 elif val[0] == 'PT': 123 pt = list(genhex(val[1])) 124 elif val[0] == 'AAD': 125 aad = list(genhex(val[1])) 126 elif val[0] == 'CT': 127 ct = list(genhex(val[1])) 128 elif val[0] == 'Tag': 129 tag = list(genhex(val[1])) 130 131 132 def print_hexbuf(buf, buflen): 133 for ind, byte in enumerate(buf): 134 if ind % 8 == 0: 135 print(end='\t') 136 print(f'{byte},', end=' ' if (ind + 1) % 8 != 0 and (ind + 1) != buflen else '\n') 137 138 i = 0 139 datastr = 'uint8_t *DATA[] = {' 140 datalenstr = 'size_t DATALEN[] = {' 141 resstr = 'uint8_t *RES[] = {' 142 reslenstr = 'size_t RESLEN[] = {' 143 ivstr = 'uint8_t *IV[] = {' 144 ivlenstr = 'size_t IVLEN[] = {' 145 keystr = 'uint8_t *KEY[] = {' 146 keylenstr = 'size_t KEYLEN[] = {' 147 for test in tests: 148 for vec in test: 149 print(f'uint8_t GMAC_KEY{i}[] = {{') 150 print_hexbuf(vec.key, test.keylen / 8) 151 print('};\n') 152 153 # For GMAC, there should be no plaintext; use AAD instead 154 if len(vec.pt) != 0: 155 raise ValueError(f'case {i} has plaintext data') 156 157 print(f'uint8_t GMAC_DATA{i}[] = {{') 158 print_hexbuf(vec.aad, test.aadlen / 8) 159 print('};\n') 160 161 print(f'uint8_t GMAC_IV{i}[] = {{') 162 print_hexbuf(vec.iv, test.ivlen / 8 ) 163 print('};\n') 164 165 print(f'uint8_t GMAC_RES{i}[] = {{') 166 print_hexbuf(vec.ct, test.ctlen / 8) 167 print('};\n') 168 169 if i % 3 == 0: 170 datalenstr += '\n\t' 171 reslenstr += '\n\t' 172 ivlenstr += '\n\t' 173 keylenstr += '\n\t' 174 elif i != 0: 175 datalenstr += ' ' 176 reslenstr += ' ' 177 ivlenstr += ' ' 178 keylenstr += ' ' 179 180 if i % 5 == 0: 181 datastr += '\n\t' 182 elif i != 0: 183 datastr += ' ' 184 185 if i % 6 == 0: 186 resstr += '\n\t' 187 ivstr += '\n\t' 188 keystr += '\n\t' 189 elif i != 0: 190 resstr += ' ' 191 ivstr += ' ' 192 keystr += ' ' 193 194 datastr += f'GMAC_DATA{i},' 195 datalenstr += f'sizeof (GMAC_DATA{i}),' 196 197 resstr += f'GMAC_RES{i},' 198 reslenstr += f'sizeof (GMAC_RES{i}),' 199 200 ivstr += f'GMAC_IV{i},' 201 ivlenstr += f'sizeof (GMAC_IV{i}),' 202 203 keystr += f'GMAC_KEY{i},' 204 keylenstr += f'sizeof (GMAC_KEY{i}),' 205 206 i += 1 207 208 print(datastr + '\n};\n') 209 print(datalenstr + '\n};\n') 210 print(resstr + '\n};\n') 211 print(reslenstr + '\n};\n') 212 print(ivstr + '\n};\n') 213 print(ivlenstr + '\n};\n') 214 print(keystr + '\n};\n') 215 print(keylenstr + '\n};\n') 216 217if __name__ == '__main__': 218 try: 219 main() 220 except KeyboardInterrupt: 221 print('Terminated by KeyboardInterrupt.') 222