hare

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

gcm.ha (6642B)


      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]]. If the 'block' should
     52 // not be finished, [[gcm_unlink_block]] must be called before close.
     53 export fn gcm() gcmstream = {
     54 	return gcmstream {
     55 		stream = &gcm_vtable,
     56 		handle = 0,
     57 		...
     58 	};
     59 };
     60 
     61 // Initialises the gcmstream. The data will be encrypted to or encrypted from
     62 // the given 'handle' The implementation only supports a block cipher 'b' with a
     63 // block size of 16 bytes. The initialization vector (nonce) 'iv' may have any
     64 // size up to 2**61 bytes. 12 bytes is the recommended size, if efficiency is
     65 // critical. The additional data 'ad' will be authenticated but not encrypted
     66 // and may have a maximum length of 2**61 - 1 bytes. 'ad' will not be written to
     67 // the underlying handle.
     68 export fn gcm_init(
     69 	s: *gcmstream,
     70 	handle: io::handle,
     71 	b: *block,
     72 	iv: const []u8,
     73 	ad: const []u8
     74 ) void = {
     75 	assert(blocksz(b) == GCMBLOCKSZ);
     76 	assert(len(iv): u64 <= (types::U64_MAX >> 3));
     77 
     78 	s.handle = handle;
     79 	s.block = b;
     80 	s.adlen = len(ad);
     81 	s.xorbufpos = GCMBLOCKSZ; // to force fill xorbuf at start
     82 
     83 	encrypt(b, s.h, s.h);
     84 
     85 	if (len(iv) == 12) {
     86 		s.y0[..12] = iv[..];
     87 		s.y0[15] |= 1;
     88 	} else {
     89 		let ivlen = s.tagbuf;
     90 		beputu64(ivlen[8..], len(iv) << 3);
     91 		ghash_ctmul64(s.y0, s.h, iv);
     92 		ghash_ctmul64(s.y0, s.h, ivlen);
     93 		bytes::zero(ivlen);
     94 	};
     95 
     96 	s.y = begetu32(s.y0[12..]) + 1;
     97 
     98 	let ad = ad[..];
     99 	for (len(ad) > 0) {
    100 		const max = if (len(ad) >= GCMBLOCKSZ) {
    101 			yield GCMBLOCKSZ;
    102 		} else {
    103 			yield len(ad);
    104 		};
    105 
    106 		ghash_ctmul64(s.tagbuf, s.h, ad[..max]);
    107 		ad = ad[max..];
    108 	};
    109 };
    110 
    111 fn gcm_writer(s: *io::stream, buf: const []u8) (size | io::error) = {
    112 	let s = s: *gcmstream;
    113 	if (len(buf) == 0) {
    114 		return 0z;
    115 	};
    116 
    117 	if (s.xorbufpos == GCMBLOCKSZ) {
    118 		// current key block is depleted, prepare the next one
    119 		fillxorbuf(s);
    120 	};
    121 
    122 	let buf = buf[..];
    123 
    124 	let n: size = 0;
    125 	const max = if (s.xorbufpos + len(buf) > len(s.cipherbuf)) {
    126 		yield len(s.cipherbuf) - s.xorbufpos;
    127 	} else {
    128 		yield len(buf);
    129 	};
    130 
    131 	let cipher = s.cipherbuf[s.xorbufpos..s.xorbufpos + max];
    132 	let key = s.xorbuf[s.xorbufpos..s.xorbufpos + max];
    133 	xor(cipher, key, buf[..max]);
    134 
    135 	const n = io::write(s.handle, cipher)?;
    136 	s.xorbufpos += n;
    137 	s.clen += n;
    138 
    139 	if (s.xorbufpos == GCMBLOCKSZ) {
    140 		ghash_ctmul64(s.tagbuf, s.h, s.cipherbuf);
    141 	};
    142 
    143 	return n;
    144 };
    145 
    146 fn fillxorbuf(s: *gcmstream) void = {
    147 	let y: [GCMBLOCKSZ]u8 = [0...];
    148 	s.xorbuf[..] = s.y0[..];
    149 	beputu32(s.xorbuf[12..], s.y);
    150 	encrypt(s.block as *block, s.xorbuf, s.xorbuf);
    151 	s.y += 1;
    152 	s.xorbufpos = 0;
    153 };
    154 
    155 fn gcm_reader(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
    156 	let s = s: *gcmstream;
    157 
    158 	const n = match (io::read(s.handle, buf)?) {
    159 	case io::EOF =>
    160 		return io::EOF;
    161 	case let s: size =>
    162 		yield s;
    163 	};
    164 
    165 	for (let i = n; i > 0) {
    166 		if (s.xorbufpos == GCMBLOCKSZ) {
    167 			fillxorbuf(s);
    168 		};
    169 
    170 		const max = if (s.xorbufpos + i > GCMBLOCKSZ) {
    171 			yield len(s.cipherbuf) - s.xorbufpos;
    172 		} else {
    173 			yield i;
    174 		};
    175 
    176 		let cipher = s.cipherbuf[s.xorbufpos..s.xorbufpos + max];
    177 		let key = s.xorbuf[s.xorbufpos..s.xorbufpos + max];
    178 
    179 		cipher[..] = buf[..max];
    180 		xor(buf[..max], buf[..max], key);
    181 
    182 		buf = buf[max..];
    183 		i -= max;
    184 
    185 		s.xorbufpos += max;
    186 		s.clen += max;
    187 
    188 		if (s.xorbufpos == len(s.cipherbuf)) {
    189 			ghash_ctmul64(s.tagbuf, s.h, s.cipherbuf);
    190 		};
    191 	};
    192 
    193 	return n;
    194 };
    195 
    196 // Finishes encryption and returns the authentication tag. After calling seal,
    197 // the user must not write any more data to the stream.
    198 export fn gcm_seal(s: *gcmstream, tag: []u8) void = {
    199 	assert(len(tag) == GCMTAGSZ);
    200 	if (s.xorbufpos > 0 && s.xorbufpos < GCMBLOCKSZ) {
    201 		// last block was is not full, therefore the content was not
    202 		// hashed yet.
    203 		ghash_ctmul64(s.tagbuf, s.h, s.cipherbuf[..s.xorbufpos]);
    204 	};
    205 
    206 	beputu64(tag, s.adlen << 3);
    207 	beputu64(tag[8..], s.clen << 3);
    208 	ghash_ctmul64(s.tagbuf, s.h, tag);
    209 
    210 	// use tmp to store the resulting tag
    211 	encrypt(s.block as *block, tag, s.y0);
    212 	xor(tag, tag, s.tagbuf);
    213 };
    214 
    215 // Verifies the authentication tag against the decrypted data. Must be called
    216 // after reading all data from the stream to ensure that the data was not
    217 // modified. If the data was modified, [[errors::invalid]] will be returned and
    218 // the data must not be trusted.
    219 export fn gcm_verify(s: *gcmstream, tag: []u8) (void | errors::invalid) = {
    220 	assert(len(tag) == GCMTAGSZ);
    221 	if (s.xorbufpos > 0 && s.xorbufpos < GCMBLOCKSZ) {
    222 		ghash_ctmul64(s.tagbuf, s.h, s.cipherbuf[..s.xorbufpos]);
    223 	};
    224 
    225 	let tmp: [16]u8 = [0...];
    226 	beputu64(tmp, s.adlen << 3);
    227 	beputu64(tmp[8..], s.clen << 3);
    228 
    229 	ghash_ctmul64(s.tagbuf, s.h, tmp);
    230 
    231 	encrypt(s.block as *block, tmp, s.y0);
    232 	xor(tmp, tmp, s.tagbuf);
    233 
    234 	if (eqslice(tag, tmp) == 0) {
    235 		return errors::invalid;
    236 	};
    237 };
    238 
    239 // Unlinks the 'block' provided by [[gcm_init]] to avoid finishing it when
    240 // calling [[io::close]] on the stream.
    241 export fn gcm_unlink_block(s: *gcmstream) void = {
    242 	s.block = null;
    243 };
    244 
    245 fn gcm_closer(s: *io::stream) (void | io::error) = {
    246 	let s = s: *gcmstream;
    247 	bytes::zero(s.tagbuf);
    248 	bytes::zero(s.xorbuf);
    249 	bytes::zero(s.cipherbuf);
    250 	bytes::zero(s.y0);
    251 	bytes::zero(s.h);
    252 
    253 	if (s.block is *block) {
    254 		finish(s.block as *block);
    255 	};
    256 };