hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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 };