hare

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

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