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