hare

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

commit df6844010948a5a9e18096615a4b3b8ed3ac4fbc
parent 5032e8d3d90b428faacfc23f31781dd7c714d497
Author: Armin Preiml <apreiml@strohwolke.at>
Date:   Tue, 23 May 2023 16:30:54 +0200

add crypto::chachapoly

Implements the AEAD stream ciphers Chacha20-Poly1305 and
XChacha20-Poly1305 with a similar interface as the GCM cipher mode.

Signed-off-by: Armin Preiml <apreiml@strohwolke.at>

Diffstat:
Acrypto/chachapoly/README | 18++++++++++++++++++
Acrypto/chachapoly/chachapoly.ha | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrypto/chachapoly/encryption+test.ha | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/gen-stdlib | 12++++++++++++
Mstdlib.mk | 33+++++++++++++++++++++++++++++++++
5 files changed, 419 insertions(+), 0 deletions(-)

diff --git a/crypto/chachapoly/README b/crypto/chachapoly/README @@ -0,0 +1,18 @@ +This module provides Chacha20-Poly1305 and XChacha20-Poly1305 stream +implementations as described in RFC 8439. + +A stream is created with [[chachapoly]]. [[init]] initializes a stream as a +Chacha20-Poly1305 one where [[xinit]] will initialize it as a +XChacha20-Poly1305 stream. After initializiation data can be encrypted by +writing to or decrypted by reading from the stream. The user must call [[seal]] +when encryption is done to create the authentication tag and [[verify]] in case +of decryption to check if the dercypted data is valid. If the data is invalid +it must not be processed further. + +This is a low-level module which implements cryptographic primitives. Direct use +of cryptographic primitives is not recommended for non-experts, as incorrect use +of these primitives can easily lead to the introduction of security +vulnerabilities. Non-experts are advised to use the high-level operations +available in the top-level [[crypto]] module. + +Be advised that Hare's cryptography implementations have not been audited. diff --git a/crypto/chachapoly/chachapoly.ha b/crypto/chachapoly/chachapoly.ha @@ -0,0 +1,175 @@ +// License: MPL-2.0 +// (c) 2023 Armin Preiml <apreiml@strohwolke.at> +use bufio; +use bytes; +use crypto::chacha; +use crypto::mac; +use crypto::math; +use crypto::poly1305; +use endian; +use errors; +use io; +use types; + +// Nonce size as required by [[init]]. +export def NONCESZ: size = chacha::NONCESIZE; + +// Nonce size as required by [[xinit]]. +export def XNONCESZ: size = chacha::XNONCESIZE; + +// Key size +export def KEYSZ: size = chacha::KEYSIZE; + +// Tag size +export def TAGSZ: size = poly1305::SIZE; + +export type stream = struct { + stream: io::stream, + h: io::teestream, + c: chacha::stream, + p: poly1305::state, + adsz: size, + msgsz: size, +}; + +// Create a stream that must be initialised by [[init]] or [[xinit]]. The user +// must call [[io::close]] when they are done using the stream to securly erase +// secret information stored in the stream state. +export fn chachapoly() stream = { + return stream { + stream = &vtable, + c = chacha::chacha20(), + p = poly1305::poly1305(), + ... + }; +}; + +const vtable: io::vtable = io::vtable { + writer = &writer, + reader = &reader, + closer = &closer, + ... +}; + +type initfunc = fn (s: *chacha::stream, h: io::handle, k: []u8, n: []u8) void; + +// Initialises the stream as Chacha20-Poly1305. Encrypts to or decrypts form +// 'h'. 'nonce' must be a random value that will only be used once. Additional +// data can be passed as 'ad'. +export fn init( + s: *stream, + h: io::handle, + key: const []u8, + nonce: const []u8, + ad: const []u8..., +) void = geninit(s, &chacha::chacha20_init, h, key, nonce, ad...); + +// Initialise the stream as XChacha20-Poly1305. Encrypts to or decrypts form +// 'h'. 'nonce' must be a random value that will only be used once. Additional +// data can be passed as 'ad'. +export fn xinit( + s: *stream, + h: io::handle, + key: const []u8, + nonce: const []u8, + ad: const []u8..., +) void = geninit(s, &chacha::xchacha20_init, h, key, nonce, ad...); + +fn geninit( + s: *stream, + finit: *initfunc, + h: io::handle, + key: const []u8, + nonce: const []u8, + ad: const []u8..., +) void = { + assert(len(ad) <= types::U32_MAX); + + let otk: poly1305::key = [0...]; + defer bytes::zero(otk); + + let otkbuf = bufio::fixed(otk, io::mode::WRITE); + finit(&s.c, &otkbuf, key, nonce); + io::writeall(&s.c, otk[..])!; + + poly1305::init(&s.p, &otk); + + s.adsz = 0z; + for (let i = 0z; i < len(ad); i += 1) { + s.adsz += len(ad[i]); + mac::write(&s.p, ad[i]); + }; + polypad(&s.p, s.adsz); + + s.h = io::tee(h, &s.p); + finit(&s.c, &s.h, key, nonce); + chacha::setctr(&s.c, 1); + + s.msgsz = 0; +}; + +fn polypad(p: *poly1305::state, n: size) void = { + if (n % poly1305::BLOCKSIZE == 0) { + return; + }; + + const pad: [poly1305::BLOCKSIZE]u8 = [0...]; + const padlen = poly1305::BLOCKSIZE - (n % poly1305::BLOCKSIZE); + mac::write(p, pad[..padlen]); +}; + +// Finishes encryption and writes the authentication code to 'tag'. After +// calling seal, the user must not write any more data to the stream. +export fn seal(s: *stream, tag: []u8) void = writemac(s, tag); + +fn writemac(s: *stream, tag: []u8) void = { + assert(len(tag) == TAGSZ); + assert(s.msgsz <= types::U32_MAX); + + polypad(&s.p, s.msgsz); + + let nbuf: [8]u8 = [0...]; + endian::leputu64(nbuf, s.adsz: u32); + mac::write(&s.p, nbuf); + + endian::leputu64(nbuf, s.msgsz: u32); + mac::write(&s.p, nbuf); + + mac::sum(&s.p, tag[..]); +}; + +// Verifies the authentication tag against the decrypted data. Must be called +// after reading all data from the stream to ensure that the data was not +// modified. If the data was modified, [[errors::invalid]] will be returned and +// the data must not be trusted. +export fn verify(s: *stream, tag: []u8) (void | errors::invalid) = { + let ntag: [TAGSZ]u8 = [0...]; + writemac(s, ntag); + if (crypto::math::eqslice(ntag, tag) == 0) { + return errors::invalid; + }; +}; + +fn writer(s: *io::stream, buf: const []u8) (size | io::error) = { + let s = s: *stream; + const n = io::write(&s.c, buf)?; + s.msgsz += n; + return n; +}; + +fn reader(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = { + let s = s: *stream; + match (io::read(&s.c, buf)?) { + case io::EOF => + return io::EOF; + case let n: size => + s.msgsz += n; + return n; + }; +}; + +fn closer(s: *io::stream) (void | io::error) = { + let s = s: *stream; + io::close(&s.c)!; + mac::finish(&s.p); +}; diff --git a/crypto/chachapoly/encryption+test.ha b/crypto/chachapoly/encryption+test.ha @@ -0,0 +1,181 @@ +// License: MPL-2.0 +// (c) 2023 Armin Preiml <apreiml@strohwolke.at> +use bufio; +use bytes; +use io; + +@test fn encrypt() void = { + let plain: [_]u8 = [ + 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, + 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, 0x6f, 0x75, + 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, + 0x6f, 0x75, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, + 0x65, 0x20, 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, + 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, 0x63, 0x72, 0x65, 0x65, + 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, + 0x20, 0x69, 0x74, 0x2e + ]; + + let ad: [_]u8 = [ + 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, + ]; + + let key: [_]u8 = [ + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, + 0x9e, 0x9f, + ]; + + let nonce: [_]u8 = [ + 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, + ]; + + let cipher: [_]u8 = [ + 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, 0x7b, 0x86, + 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, 0xa4, 0xad, 0xed, 0x51, + 0x29, 0x6e, 0x08, 0xfe, 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, + 0x62, 0xd6, 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, + 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, 0x1a, 0x71, + 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, 0x05, 0xd6, 0xa5, 0xb6, + 0x7e, 0xcd, 0x3b, 0x36, 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, + 0x8b, 0x8c, 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58, + 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, 0x55, 0x85, + 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc, 0x3f, 0xf4, 0xde, 0xf0, + 0x8e, 0x4b, 0x7a, 0x9d, 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, + 0xc6, 0x4b, 0x61, 0x16, + ]; + + let tag: [_]u8 = [ + 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 0x7e, 0x90, + 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91, + ]; + + let out = bufio::dynamic(io::mode::RDWR); + defer io::close(&out)!; + + let s = chachapoly(); + init(&s, &out, key, nonce, ad); + io::writeall(&s, plain)!; + + let outtag: [TAGSZ]u8 = [0...]; + seal(&s, outtag); + + let outbuf = bufio::buffer(&out); + assert(bytes::equal(outbuf, cipher)); + assert(bytes::equal(outtag, tag)); + + let out = bufio::dynamic(io::mode::RDWR); + defer io::close(&out)!; + let in = bufio::fixed(cipher, io::mode::READ); + + let s = chachapoly(); + init(&s, &in, key, nonce, ad); + io::copy(&out, &s)!; + + verify(&s, tag)!; + + let outbuf = bufio::buffer(&out); + assert(bytes::equal(outbuf, plain)); + + io::close(&s)!; +}; + +type sample = struct { + msg: []u8, + cipher: []u8, + additional: []u8, + key: []u8, + nonce: []u8, + mac: []u8, +}; + +// test vector taken from the XChacha20-Poly1305-AEAD draft rfc +const rfcsample: sample = sample { + msg = [ + 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, + 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, 0x6f, 0x75, + 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, + 0x6f, 0x75, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, + 0x65, 0x20, 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, + 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, 0x63, 0x72, 0x65, 0x65, + 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, + 0x20, 0x69, 0x74, 0x2e, + ], + additional = [ + 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, + ], + key = [ + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, + 0x9e, 0x9f, + ], + nonce = [ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, + ], + cipher = [ + 0xbd, 0x6d, 0x17, 0x9d, 0x3e, 0x83, 0xd4, 0x3b, 0x95, 0x76, + 0x57, 0x94, 0x93, 0xc0, 0xe9, 0x39, 0x57, 0x2a, 0x17, 0x00, + 0x25, 0x2b, 0xfa, 0xcc, 0xbe, 0xd2, 0x90, 0x2c, 0x21, 0x39, + 0x6c, 0xbb, 0x73, 0x1c, 0x7f, 0x1b, 0x0b, 0x4a, 0xa6, 0x44, + 0x0b, 0xf3, 0xa8, 0x2f, 0x4e, 0xda, 0x7e, 0x39, 0xae, 0x64, + 0xc6, 0x70, 0x8c, 0x54, 0xc2, 0x16, 0xcb, 0x96, 0xb7, 0x2e, + 0x12, 0x13, 0xb4, 0x52, 0x2f, 0x8c, 0x9b, 0xa4, 0x0d, 0xb5, + 0xd9, 0x45, 0xb1, 0x1b, 0x69, 0xb9, 0x82, 0xc1, 0xbb, 0x9e, + 0x3f, 0x3f, 0xac, 0x2b, 0xc3, 0x69, 0x48, 0x8f, 0x76, 0xb2, + 0x38, 0x35, 0x65, 0xd3, 0xff, 0xf9, 0x21, 0xf9, 0x66, 0x4c, + 0x97, 0x63, 0x7d, 0xa9, 0x76, 0x88, 0x12, 0xf6, 0x15, 0xc6, + 0x8b, 0x13, 0xb5, 0x2e, + ], + mac = [ + 0xc0, 0x87, 0x59, 0x24, 0xc1, 0xc7, 0x98, 0x79, 0x47, 0xde, + 0xaf, 0xd8, 0x78, 0x0a, 0xcf, 0x49, + ], +}; + +@test fn xencrypt() void = { + let tc = rfcsample; + + let out = bufio::dynamic(io::mode::RDWR); + defer io::close(&out)!; + + let s = chachapoly(); + xinit(&s, &out, tc.key, tc.nonce, tc.additional); + io::writeall(&s, tc.msg)!; + + let outtag: [TAGSZ]u8 = [0...]; + seal(&s, outtag); + + let outbuf = bufio::buffer(&out); + assert(bytes::equal(outbuf, tc.cipher)); + assert(bytes::equal(outtag, tc.mac)); + + let out = bufio::dynamic(io::mode::RDWR); + defer io::close(&out)!; + let in = bufio::fixed(tc.cipher, io::mode::READ); + + let s = chachapoly(); + xinit(&s, &in, tc.key, tc.nonce, tc.additional); + io::copy(&out, &s)!; + + verify(&s, tc.mac)!; + + let outbuf = bufio::buffer(&out); + assert(bytes::equal(outbuf, tc.msg)); + + io::close(&s)!; +}; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -341,6 +341,17 @@ crypto_chacha() { fi } +crypto_chachapoly() { + if [ $testing -eq 0 ] + then + gen_srcs crypto::chachapoly chachapoly.ha + else + gen_srcs crypto::chachapoly chachapoly.ha encryption+test.ha + fi + gen_ssa crypto::chachapoly bufio bytes crypto::chacha crypto::mac \ + crypto::math crypto::poly1305 endian errors io types +} + crypto_cipher() { gen_srcs crypto::cipher \ cipher.ha \ @@ -1551,6 +1562,7 @@ crypto::blake2b crypto::blowfish crypto::bigint crypto::chacha +crypto::chachapoly crypto::cipher crypto::hkdf crypto::hmac diff --git a/stdlib.mk b/stdlib.mk @@ -217,6 +217,12 @@ stdlib_deps_any += $(stdlib_crypto_chacha_any) stdlib_crypto_chacha_linux = $(stdlib_crypto_chacha_any) stdlib_crypto_chacha_freebsd = $(stdlib_crypto_chacha_any) +# gen_lib crypto::chachapoly (any) +stdlib_crypto_chachapoly_any = $(HARECACHE)/crypto/chachapoly/crypto_chachapoly-any.o +stdlib_deps_any += $(stdlib_crypto_chachapoly_any) +stdlib_crypto_chachapoly_linux = $(stdlib_crypto_chachapoly_any) +stdlib_crypto_chachapoly_freebsd = $(stdlib_crypto_chachapoly_any) + # gen_lib crypto::cipher (any) stdlib_crypto_cipher_any = $(HARECACHE)/crypto/cipher/crypto_cipher-any.o stdlib_env += HARE_TD_crypto::cipher=$(HARECACHE)/crypto/cipher/crypto_cipher.td @@ -1025,6 +1031,16 @@ $(HARECACHE)/crypto/chacha/crypto_chacha-any.ssa: $(stdlib_crypto_chacha_any_src @$(stdlib_env) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::chacha \ -t$(HARECACHE)/crypto/chacha/crypto_chacha.td $(stdlib_crypto_chacha_any_srcs) +# crypto::chachapoly (+any) +stdlib_crypto_chachapoly_any_srcs = \ + $(STDLIB)/crypto/chachapoly/chachapoly.ha + +$(HARECACHE)/crypto/chachapoly/crypto_chachapoly-any.ssa: $(stdlib_crypto_chachapoly_any_srcs) $(stdlib_rt) $(stdlib_bufio_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM)) $(stdlib_crypto_chacha_$(PLATFORM)) $(stdlib_crypto_mac_$(PLATFORM)) $(stdlib_crypto_math_$(PLATFORM)) $(stdlib_crypto_poly1305_$(PLATFORM)) $(stdlib_endian_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_types_$(PLATFORM)) + @printf 'HAREC \t$@\n' + @mkdir -p $(HARECACHE)/crypto/chachapoly + @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::chachapoly \ + -t$(HARECACHE)/crypto/chachapoly/crypto_chachapoly.td $(stdlib_crypto_chachapoly_any_srcs) + # crypto::cipher (+any) stdlib_crypto_cipher_any_srcs = \ $(STDLIB)/crypto/cipher/cipher.ha \ @@ -2608,6 +2624,12 @@ testlib_deps_any += $(testlib_crypto_chacha_any) testlib_crypto_chacha_linux = $(testlib_crypto_chacha_any) testlib_crypto_chacha_freebsd = $(testlib_crypto_chacha_any) +# gen_lib crypto::chachapoly (any) +testlib_crypto_chachapoly_any = $(TESTCACHE)/crypto/chachapoly/crypto_chachapoly-any.o +testlib_deps_any += $(testlib_crypto_chachapoly_any) +testlib_crypto_chachapoly_linux = $(testlib_crypto_chachapoly_any) +testlib_crypto_chachapoly_freebsd = $(testlib_crypto_chachapoly_any) + # gen_lib crypto::cipher (any) testlib_crypto_cipher_any = $(TESTCACHE)/crypto/cipher/crypto_cipher-any.o testlib_env += HARE_TD_crypto::cipher=$(TESTCACHE)/crypto/cipher/crypto_cipher.td @@ -3433,6 +3455,17 @@ $(TESTCACHE)/crypto/chacha/crypto_chacha-any.ssa: $(testlib_crypto_chacha_any_sr @$(testlib_env) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncrypto::chacha \ -t$(TESTCACHE)/crypto/chacha/crypto_chacha.td $(testlib_crypto_chacha_any_srcs) +# crypto::chachapoly (+any) +testlib_crypto_chachapoly_any_srcs = \ + $(STDLIB)/crypto/chachapoly/chachapoly.ha \ + $(STDLIB)/crypto/chachapoly/encryption+test.ha + +$(TESTCACHE)/crypto/chachapoly/crypto_chachapoly-any.ssa: $(testlib_crypto_chachapoly_any_srcs) $(testlib_rt) $(testlib_bufio_$(PLATFORM)) $(testlib_bytes_$(PLATFORM)) $(testlib_crypto_chacha_$(PLATFORM)) $(testlib_crypto_mac_$(PLATFORM)) $(testlib_crypto_math_$(PLATFORM)) $(testlib_crypto_poly1305_$(PLATFORM)) $(testlib_endian_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_types_$(PLATFORM)) + @printf 'HAREC \t$@\n' + @mkdir -p $(TESTCACHE)/crypto/chachapoly + @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncrypto::chachapoly \ + -t$(TESTCACHE)/crypto/chachapoly/crypto_chachapoly.td $(testlib_crypto_chachapoly_any_srcs) + # crypto::cipher (+any) testlib_crypto_cipher_any_srcs = \ $(STDLIB)/crypto/cipher/cipher.ha \