hare

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

chachapoly.ha (4224B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bytes;
      5 use crypto::chacha;
      6 use crypto::mac;
      7 use crypto::math;
      8 use crypto::poly1305;
      9 use endian;
     10 use errors;
     11 use io;
     12 use memio;
     13 use types;
     14 
     15 // Nonce size as required by [[init]].
     16 export def NONCESZ: size = chacha::NONCESZ;
     17 
     18 // Nonce size as required by [[xinit]].
     19 export def XNONCESZ: size = chacha::XNONCESZ;
     20 
     21 // Key size
     22 export def KEYSZ: size = chacha::KEYSZ;
     23 
     24 // Tag size
     25 export def TAGSZ: size = poly1305::SZ;
     26 
     27 export type stream = struct {
     28 	stream: io::stream,
     29 	h: io::teestream,
     30 	c: chacha::stream,
     31 	p: poly1305::state,
     32 	adsz: size,
     33 	msgsz: size,
     34 };
     35 
     36 // Create a stream that must be initialised by [[init]] or [[xinit]]. The user
     37 // must call [[io::close]] when they are done using the stream to securly erase
     38 // secret information stored in the stream state.
     39 export fn chachapoly() stream = {
     40 	return stream {
     41 		stream = &vtable,
     42 		c = chacha::chacha20(),
     43 		p = poly1305::poly1305(),
     44 		h = io::tee(io::empty, io::empty),
     45 		...
     46 	};
     47 };
     48 
     49 const vtable: io::vtable = io::vtable {
     50 	writer = &writer,
     51 	reader = &reader,
     52 	closer = &closer,
     53 	...
     54 };
     55 
     56 type initfunc = fn (s: *chacha::stream, h: io::handle, k: []u8, n: []u8) void;
     57 
     58 // Initialises the stream as Chacha20-Poly1305. Encrypts to or decrypts from
     59 // 'h'. 'nonce' must be a random value that will only be used once. Additional
     60 // data can be passed as 'ad'.
     61 export fn init(
     62 	s: *stream,
     63 	h: io::handle,
     64 	key: const []u8,
     65 	nonce: const []u8,
     66 	ad: const []u8...
     67 ) void = geninit(s, &chacha::chacha20_init, h, key, nonce, ad...);
     68 
     69 // Initialise the stream as XChacha20-Poly1305. Encrypts to or decrypts from
     70 // 'h'. 'nonce' must be a random value that will only be used once. Additional
     71 // data can be passed as 'ad'.
     72 export fn xinit(
     73 	s: *stream,
     74 	h: io::handle,
     75 	key: const []u8,
     76 	nonce: const []u8,
     77 	ad: const []u8...
     78 ) void = geninit(s, &chacha::xchacha20_init, h, key, nonce, ad...);
     79 
     80 fn geninit(
     81 	s: *stream,
     82 	finit: *initfunc,
     83 	h: io::handle,
     84 	key: const []u8,
     85 	nonce: const []u8,
     86 	ad: const []u8...
     87 ) void = {
     88 	assert(len(ad) <= types::U32_MAX);
     89 
     90 	let otk: poly1305::key = [0...];
     91 	defer bytes::zero(otk);
     92 
     93 	let otkbuf = memio::fixed(otk);
     94 	finit(&s.c, &otkbuf, key, nonce);
     95 	io::writeall(&s.c, otk[..])!;
     96 
     97 	poly1305::init(&s.p, &otk);
     98 
     99 	s.adsz = 0z;
    100 	for (let i = 0z; i < len(ad); i += 1) {
    101 		s.adsz += len(ad[i]);
    102 		mac::write(&s.p, ad[i]);
    103 	};
    104 	polypad(&s.p, s.adsz);
    105 
    106 	s.h = io::tee(h, &s.p);
    107 	finit(&s.c, &s.h, key, nonce);
    108 	chacha::setctr(&s.c, 1);
    109 
    110 	s.msgsz = 0;
    111 };
    112 
    113 fn polypad(p: *poly1305::state, n: size) void = {
    114 	if (n % poly1305::BLOCKSZ == 0) {
    115 		return;
    116 	};
    117 
    118 	const pad: [poly1305::BLOCKSZ]u8 = [0...];
    119 	const padlen = poly1305::BLOCKSZ - (n % poly1305::BLOCKSZ);
    120 	mac::write(p, pad[..padlen]);
    121 };
    122 
    123 // Finishes encryption and writes the authentication code to 'tag'. After
    124 // calling seal, the user must not write any more data to the stream.
    125 export fn seal(s: *stream, tag: []u8) void = writemac(s, tag);
    126 
    127 fn writemac(s: *stream, tag: []u8) void = {
    128 	assert(len(tag) == TAGSZ);
    129 	assert(s.msgsz <= types::U32_MAX);
    130 
    131 	polypad(&s.p, s.msgsz);
    132 
    133 	let nbuf: [8]u8 = [0...];
    134 	endian::leputu64(nbuf, s.adsz: u32);
    135 	mac::write(&s.p, nbuf);
    136 
    137 	endian::leputu64(nbuf, s.msgsz: u32);
    138 	mac::write(&s.p, nbuf);
    139 
    140 	mac::sum(&s.p, tag[..]);
    141 };
    142 
    143 // Verifies the authentication tag against the decrypted data. Must be called
    144 // after reading all data from the stream to ensure that the data was not
    145 // modified. If the data was modified, [[errors::invalid]] will be returned and
    146 // the data must not be trusted.
    147 export fn verify(s: *stream, tag: []u8) (void | errors::invalid) = {
    148 	let ntag: [TAGSZ]u8 = [0...];
    149 	writemac(s, ntag);
    150 	if (math::eqslice(ntag, tag) == 0) {
    151 		return errors::invalid;
    152 	};
    153 };
    154 
    155 fn writer(s: *io::stream, buf: const []u8) (size | io::error) = {
    156 	let s = s: *stream;
    157 	const n = io::write(&s.c, buf)?;
    158 	s.msgsz += n;
    159 	return n;
    160 };
    161 
    162 fn reader(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
    163 	let s = s: *stream;
    164 	match (io::read(&s.c, buf)?) {
    165 	case io::EOF =>
    166 		return io::EOF;
    167 	case let n: size =>
    168 		s.msgsz += n;
    169 		return n;
    170 	};
    171 };
    172 
    173 fn closer(s: *io::stream) (void | io::error) = {
    174 	let s = s: *stream;
    175 	io::close(&s.c)!;
    176 	mac::finish(&s.p);
    177 };