gcm.ha (6393B)
1 // License: MPL-2.0 2 // (c) 2022 Armin Preiml <apreiml@strohwolke.at> 3 use bytes; 4 use crypto::math::{xor,eqslice}; 5 use endian::{beputu64, beputu32, begetu32}; 6 use errors; 7 use io; 8 use types; 9 10 def GCMBLOCKSIZE: size = 16; 11 12 export def GCMTAGSIZE: size = 16; 13 14 export type gcmstream = struct { 15 stream: io::stream, 16 block: nullable *block, 17 handle: io::handle, 18 tagbuf: [GCMBLOCKSIZE]u8, 19 xorbuf: [GCMBLOCKSIZE]u8, 20 cipherbuf: [GCMBLOCKSIZE]u8, 21 y0: [GCMBLOCKSIZE]u8, 22 h: [GCMBLOCKSIZE]u8, 23 y: u32, 24 xorbufpos: size, 25 adlen: u64, 26 clen: u64, 27 }; 28 29 const gcm_vtable: io::vtable = io::vtable { 30 writer = &gcm_writer, 31 reader = &gcm_reader, 32 closer = &gcm_closer, 33 ... 34 }; 35 36 // Creates a Galois Counter Mode (GCM) io::stream which can be used for 37 // encryption (by encrypting writes to the underlying handle) or decryption (or 38 // by decrypting reads from the underlying handle), but not both. [[gcm_init]] 39 // must be called to initialize the stream, before reading or writing. To 40 // authenticate the encrypted data an authentication tag must be created using 41 // [[gcm_seal]] after the encryption step. The authentication tag must be passed 42 // to [[gcm_verify]] after decryption to make sure that the encrypted and 43 // additional data were not modified. In case of a verification fail the 44 // decrypted data must not be trusted and hence discarded. 45 // 46 // A maximum of 2**36-32 bytes may be encrypted. 47 // 48 // The user must call [[io::close]] when they are done using the stream to 49 // securely erase secret information stored in the stream state. Close will 50 // also finish the 'block' provided by [[gcm_init]]. 51 export fn gcm() gcmstream = { 52 return gcmstream { 53 stream = &gcm_vtable, 54 handle = 0, 55 ... 56 }; 57 }; 58 59 // Initialises the gcmstream. The data will be encrypted to or encrypted from 60 // the given 'handle' The implementation only supports a block cipher 'b' with a 61 // block size of 16 bytes. The initialization vector (nonce) 'iv' may have any 62 // size up to 2**61 bytes. 12 bytes is the recommended size, if efficiency is 63 // critical. The additional data 'ad' will be authenticated but not encrypted 64 // and may have a maximum length of 2**61 - 1 bytes. 'ad' will not be written to 65 // the underlying handle. 66 export fn gcm_init( 67 s: *gcmstream, 68 handle: io::handle, 69 b: *block, 70 iv: const []u8, 71 ad: const []u8 72 ) void = { 73 assert(blocksz(b) == GCMBLOCKSIZE); 74 assert(len(iv): u64 <= (types::U64_MAX >> 3)); 75 76 s.handle = handle; 77 s.block = b; 78 s.adlen = len(ad); 79 s.xorbufpos = GCMBLOCKSIZE; // to force fill xorbuf at start 80 81 encrypt(b, s.h, s.h); 82 83 if (len(iv) == 12) { 84 s.y0[..12] = iv[..]; 85 s.y0[15] |= 1; 86 } else { 87 let ivlen = s.tagbuf; 88 beputu64(ivlen[8..], len(iv) << 3); 89 ghash_ctmul64(s.y0, s.h, iv); 90 ghash_ctmul64(s.y0, s.h, ivlen); 91 bytes::zero(ivlen); 92 }; 93 94 s.y = begetu32(s.y0[12..]) + 1; 95 96 let ad = ad[..]; 97 for (len(ad) > 0) { 98 const max = if (len(ad) >= GCMBLOCKSIZE) { 99 yield GCMBLOCKSIZE; 100 } else { 101 yield len(ad); 102 }; 103 104 ghash_ctmul64(s.tagbuf, s.h, ad[..max]); 105 ad = ad[max..]; 106 }; 107 }; 108 109 fn gcm_writer(s: *io::stream, buf: const []u8) (size | io::error) = { 110 let s = s: *gcmstream; 111 if (len(buf) == 0) { 112 return 0z; 113 }; 114 115 if (s.xorbufpos == GCMBLOCKSIZE) { 116 // current key block is depleted, prepare the next one 117 fillxorbuf(s); 118 }; 119 120 let buf = buf[..]; 121 122 let n: size = 0; 123 const max = if (s.xorbufpos + len(buf) > len(s.cipherbuf)) { 124 yield len(s.cipherbuf) - s.xorbufpos; 125 } else { 126 yield len(buf); 127 }; 128 129 let cipher = s.cipherbuf[s.xorbufpos..s.xorbufpos + max]; 130 let key = s.xorbuf[s.xorbufpos..s.xorbufpos + max]; 131 xor(cipher, key, buf[..max]); 132 133 const n = io::write(s.handle, cipher)?; 134 s.xorbufpos += n; 135 s.clen += n; 136 137 if (s.xorbufpos == GCMBLOCKSIZE) { 138 ghash_ctmul64(s.tagbuf, s.h, s.cipherbuf); 139 }; 140 141 return n; 142 }; 143 144 fn fillxorbuf(s: *gcmstream) void = { 145 let y: [GCMBLOCKSIZE]u8 = [0...]; 146 s.xorbuf[..] = s.y0[..]; 147 beputu32(s.xorbuf[12..], s.y); 148 encrypt(s.block as *block, s.xorbuf, s.xorbuf); 149 s.y += 1; 150 s.xorbufpos = 0; 151 }; 152 153 fn gcm_reader(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = { 154 let s = s: *gcmstream; 155 156 const n = match (io::read(s.handle, buf)?) { 157 case io::EOF => 158 return io::EOF; 159 case let s: size => 160 yield s; 161 }; 162 163 for (let i = n; i > 0) { 164 if (s.xorbufpos == GCMBLOCKSIZE) { 165 fillxorbuf(s); 166 }; 167 168 const max = if (s.xorbufpos + i > GCMBLOCKSIZE) { 169 yield len(s.cipherbuf) - s.xorbufpos; 170 } else { 171 yield i; 172 }; 173 174 let cipher = s.cipherbuf[s.xorbufpos..s.xorbufpos + max]; 175 let key = s.xorbuf[s.xorbufpos..s.xorbufpos + max]; 176 177 cipher[..] = buf[..max]; 178 xor(buf[..max], buf[..max], key); 179 180 buf = buf[max..]; 181 i -= max; 182 183 s.xorbufpos += max; 184 s.clen += max; 185 186 if (s.xorbufpos == len(s.cipherbuf)) { 187 ghash_ctmul64(s.tagbuf, s.h, s.cipherbuf); 188 }; 189 }; 190 191 return n; 192 }; 193 194 // Finishes encryption and returns the authentication tag. After calling seal, 195 // the user must not write any more data to the stream. 196 export fn gcm_seal(s: *gcmstream, tag: []u8) void = { 197 assert(len(tag) == GCMTAGSIZE); 198 if (s.xorbufpos > 0 && s.xorbufpos < GCMBLOCKSIZE) { 199 // last block was is not full, therefore the content was not 200 // hashed yet. 201 ghash_ctmul64(s.tagbuf, s.h, s.cipherbuf[..s.xorbufpos]); 202 }; 203 204 beputu64(tag, s.adlen << 3); 205 beputu64(tag[8..], s.clen << 3); 206 ghash_ctmul64(s.tagbuf, s.h, tag); 207 208 // use tmp to store the resulting tag 209 encrypt(s.block as *block, tag, s.y0); 210 xor(tag, tag, s.tagbuf); 211 }; 212 213 // Verifies the authentication tag against the decrypted data. Must be called 214 // after reading all data from the stream to ensure that the data was not 215 // modified. If the data was modified, [[errors::invalid]] will be returned and 216 // the data must not be trusted. 217 export fn gcm_verify(s: *gcmstream, tag: []u8) (void | errors::invalid) = { 218 assert(len(tag) == GCMTAGSIZE); 219 if (s.xorbufpos > 0 && s.xorbufpos < GCMBLOCKSIZE) { 220 ghash_ctmul64(s.tagbuf, s.h, s.cipherbuf[..s.xorbufpos]); 221 }; 222 223 let tmp: [16]u8 = [0...]; 224 beputu64(tmp, s.adlen << 3); 225 beputu64(tmp[8..], s.clen << 3); 226 227 ghash_ctmul64(s.tagbuf, s.h, tmp); 228 229 encrypt(s.block as *block, tmp, s.y0); 230 xor(tmp, tmp, s.tagbuf); 231 232 if (eqslice(tag, tmp) == 0) { 233 return errors::invalid; 234 }; 235 }; 236 237 fn gcm_closer(s: *io::stream) (void | io::error) = { 238 let s = s: *gcmstream; 239 bytes::zero(s.tagbuf); 240 bytes::zero(s.xorbuf); 241 bytes::zero(s.cipherbuf); 242 bytes::zero(s.y0); 243 bytes::zero(s.h); 244 245 if (s.block is *block) { 246 finish(s.block as *block); 247 }; 248 };