hare

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

gcm.ha (6232B)


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