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