commit 2d906b2620654a731bddcf407073d236f916d351
parent 203c0c02076c2ef203b2e3850b06a0abb2354ae2
Author: Armin Preiml <apreiml@strohwolke.at>
Date: Sat, 7 May 2022 14:41:17 +0200
refactor crypto::aes::ctr to be a xorstream
so that it is usable like an io::stream.
Signed-off-by: Armin Preiml <apreiml@strohwolke.at>
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
4 files changed, 249 insertions(+), 66 deletions(-)
diff --git a/crypto/aes/ctr+test.ha b/crypto/aes/ctr+test.ha
@@ -1,10 +1,13 @@
// License: MPL-2.0
-// (c) 2021 Armin Preiml <apreiml@strohwolke.at>
-// (c) 2021 Drew DeVault <sir@cmpwn.com>
-use crypto::cipher;
+// (c) 2022 Armin Preiml <apreiml@strohwolke.at>
+// (c) 2022 Drew DeVault <sir@cmpwn.com>
use bytes;
+use bufio;
+use crypto::cipher;
+use errors;
+use io;
-@test fn ctr_encrypt_zero_iv() void = {
+@test fn ctr_zero_iv() void = {
const key: [_]u8 = [
0xc3, 0x43, 0x2a, 0xf7, 0xcf, 0x56, 0x72, 0xad,
0x0f, 0x4d, 0xab, 0xee, 0xf5, 0x32, 0x0e, 0x33,
@@ -27,16 +30,30 @@ use bytes;
let result: [16]u8 = [0...];
let buf: [CTR_BUFSIZE]u8 = [0...];
+ let resultbuf = bufio::fixed(result, io::mode::WRITE);
let b = ct64();
ct64_init(&b, key);
defer cipher::finish(&b);
- let ctr = cipher::ctr(&b, iv[..], buf[..]);
- defer cipher::finish(&ctr);
+ let ctr = cipher::ctr(&resultbuf, &b, iv[..], buf[..]);
- cipher::stream_xor(&ctr, result, plain);
+ const s = io::writeall(&ctr, plain)!;
+ assert(s == len(plain));
assert(bytes::equal(cipher, result));
+ io::close(&ctr)!;
+
+ const zero: [CTR_BUFSIZE]u8 = [0...];
+ const bsz = cipher::blocksz(&b);
+ assert(bytes::equal(ctr.xorbuf, zero[bsz..]));
+
+ result = [0...];
+ buf = [0...];
+ let cipherbuf = bufio::fixed(cipher, io::mode::READ);
+ let ctr = cipher::ctr(&cipherbuf, &b, iv[..], buf[..]);
+ const s = io::readall(&ctr, result)!;
+ assert(s as size == len(plain));
+ assert(bytes::equal(plain, result));
};
@test fn ctr_encrypt_max_iv() void = {
@@ -63,16 +80,17 @@ use bytes;
];
let result: [18]u8 = [0...];
+ let resultbuf = bufio::fixed(result, io::mode::WRITE);
let buf: [CTR_BUFSIZE]u8 = [0...];
let b = ct64();
ct64_init(&b, key);
defer cipher::finish(&b);
- let ctr = cipher::ctr(&b, iv[..], buf[..]);
- defer cipher::finish(&ctr);
+ let ctr = cipher::ctr(&resultbuf, &b, iv[..], buf[..]);
+ defer io::close(&ctr)!;
- cipher::stream_xor(&ctr, result, plain);
+ io::write(&ctr, plain)!;
assert(bytes::equal(cipher, result));
};
@@ -114,17 +132,24 @@ use bytes;
];
let result: [80]u8 = [0...];
+ let resultbuf = bufio::fixed(result, io::mode::WRITE);
let buf: [CTR_BUFSIZE]u8 = [0...];
let b = ct64();
ct64_init(&b, key);
defer cipher::finish(&b);
- let ctr = cipher::ctr(&b, iv[..], buf[..]);
- defer cipher::finish(&ctr);
-
- cipher::stream_xor(&ctr, result, plain);
+ let ctr = cipher::ctr(&resultbuf, &b, iv[..], buf[..]);
+ const n = io::writeall(&ctr, plain)!;
+ assert(n == len(cipher));
assert(bytes::equal(cipher, result));
+ io::close(&ctr)!;
+
+ let cipherbuf = bufio::fixed(cipher, io::mode::READ);
+ let ctr = cipher::ctr(&cipherbuf, &b, iv[..], buf[..]);
+ const n = io::readall(&ctr, result)!;
+ assert(n as size == len(plain));
+ assert(bytes::equal(plain, result));
};
@test fn ctr_test_multiple_calls() void = {
@@ -165,23 +190,28 @@ use bytes;
];
let result: [80]u8 = [0...];
+ let resultbuf = bufio::fixed(result, io::mode::WRITE);
let buf: [CTR_BUFSIZE]u8 = [0...];
let b = ct64();
ct64_init(&b, key);
defer cipher::finish(&b);
- let ctr = cipher::ctr(&b, iv[..], buf[..]);
- defer cipher::finish(&ctr);
- cipher::stream_xor(&ctr, result[0..32], plain[0..32]);
- cipher::stream_xor(&ctr, result[32..], plain[32..]);
+ let ctr = cipher::ctr(&resultbuf, &b, iv[..], buf[..]);
+ defer io::close(&ctr)!;
+ let n = 0z;
+ n += io::write(&ctr, plain[0..32])!;
+ n += io::write(&ctr, plain[32..])!;
+ // previous call only writes 32 bytes. Write remaining here.
+ n += io::write(&ctr, plain[64..])!;
+ assert(n == len(plain));
assert(bytes::equal(cipher, result));
- // test nparallel blocks
- ctr = cipher::ctr(&b, iv[..], buf[..]);
- cipher::stream_xor(&ctr, result[0..64], plain[0..64]);
- cipher::stream_xor(&ctr, result[64..], plain[64..]);
- assert(bytes::equal(cipher, result));
+ let cipherbuf = bufio::fixed(cipher, io::mode::READ);
+ let ctr = cipher::ctr(&cipherbuf, &b, iv[..], buf[..]);
+ const n = io::readall(&ctr, result)!;
+ assert(n as size == len(plain));
+ assert(bytes::equal(plain, result));
};
@test fn ctr_encrypt_in_place() void = {
@@ -212,10 +242,11 @@ use bytes;
defer cipher::finish(&b);
let buf: [CTR_BUFSIZE]u8 = [0...];
- let ctr = cipher::ctr(&b, iv[..], buf[..]);
- defer cipher::finish(&ctr);
+ let resultbuf = bufio::fixed(result, io::mode::WRITE);
+ let ctr = cipher::ctr(&resultbuf, &b, iv[..], buf[..]);
+ defer io::close(&ctr)!;
- cipher::stream_xor(&ctr, result, result);
+ io::write(&ctr, result)!;
assert(bytes::equal(cipher, result));
};
@@ -247,9 +278,145 @@ use bytes;
defer cipher::finish(&b);
let buf: [64]u8 = [0...];
- let ctr = cipher::ctr(&b, iv[..], buf[..]);
- defer cipher::finish(&ctr);
+ let resultbuf = bufio::fixed(result, io::mode::WRITE);
+
+ let ctr = cipher::ctr(&resultbuf, &b, iv[..], buf[..]);
+ defer io::close(&ctr)!;
+
+ io::write(&ctr, result)!;
+ assert(bytes::equal(cipher, result));
+};
+
+@test fn empty_write() void = {
+ const key: [_]u8 = [
+ 0x3f, 0xf8, 0x68, 0x06, 0xe4, 0xcc, 0x88, 0x11,
+ 0xa6, 0xba, 0x14, 0xb6, 0x0b, 0x4c, 0x5a, 0xef,
+ ];
+
+ const iv: [_]u8 = [
+ 0xc5, 0x4c, 0x99, 0xd2, 0xd0, 0xef, 0xf5, 0xde,
+ 0x95, 0x38, 0x45, 0x34, 0xeb, 0xa2, 0xad, 0xa0,
+ ];
+
+ let b = ct64();
+ ct64_init(&b, key);
+ defer cipher::finish(&b);
+
+ let buf: [64]u8 = [0...];
+ let result: [1]u8 = [0];
+ let resultbuf = bufio::fixed(result, io::mode::WRITE);
+
+ let ctr = cipher::ctr(&resultbuf, &b, iv[..], buf[..]);
+ defer io::close(&ctr)!;
+
+ const n = io::write(&ctr, [])!;
+ assert(n == 0);
+};
+
+type err_stream = struct {
+ stream: io::stream,
+ out: io::handle,
+ err: io::error,
+ limit: size,
+};
+
+const err_stream_vtable: io::vtable = io::vtable {
+ writer = &err_writer,
+ ...
+};
+
+fn err_writer(s: *io::stream, buf: const []u8) (size | io::error) = {
+ let s = s: *err_stream;
+
+ if (s.limit == 0) {
+ return s.err;
+ };
+
+ const n = if (len(buf) < s.limit) {
+ yield len(buf);
+ } else {
+ return s.err;
+ };
+
+ match(io::write(s.out, buf[..n])) {
+ case let z: size =>
+ s.limit -= z;
+ return n;
+ case let e: io::error =>
+ return e;
+ };
+};
+
+fn errwriter(out: io::handle, limit: size, err: io::error) err_stream = {
+ return err_stream {
+ stream = &err_stream_vtable,
+ out = out,
+ limit = limit,
+ err = err,
+ };
+};
+
+@test fn ctr_test_retry() void = {
+ const key: [_]u8 = [
+ 0xae, 0x0c, 0x47, 0x6d, 0xce, 0x69, 0xdf, 0x52,
+ 0xf7, 0x5e, 0x1f, 0x16, 0x7e, 0xea, 0x1c, 0xf0,
+ ];
+
+ const iv: [_]u8 = [
+ 0x9a, 0x11, 0xd7, 0x24, 0x43, 0x86, 0x50, 0xf0,
+ 0xd9, 0x8c, 0x0d, 0x0d, 0x7a, 0x2e, 0x95, 0x72,
+ ];
+
+ const plain: [_]u8 = [
+ 0x05, 0xc8, 0x4a, 0xf7, 0xba, 0x4b, 0xc2, 0x4f,
+ 0xd3, 0x63, 0xf3, 0x20, 0x51, 0x2b, 0x65, 0xec,
+ 0x4c, 0xe7, 0x7f, 0x78, 0x17, 0x45, 0x0c, 0x6e,
+ 0xff, 0xf1, 0x73, 0x97, 0x42, 0x4d, 0x4a, 0x30,
+ 0x5d, 0xb2, 0x79, 0x45, 0xca, 0x20, 0x10, 0x5b,
+ 0x2c, 0x34, 0x4c, 0xed, 0x42, 0x59, 0x61, 0x3a,
+ 0x66, 0xa3, 0x2e, 0x74, 0xa8, 0xbb, 0x9c, 0xe9,
+ 0x4e, 0xda, 0x33, 0x97, 0x98, 0x41, 0x9a, 0xf0,
+ 0xbb, 0x6d, 0xcb, 0x5d, 0x15, 0x60, 0x26, 0x7c,
+ 0x6c, 0xe5, 0xa4, 0xaf, 0x52, 0x14, 0x29, 0x2f,
+ ];
+
+ const cipher: [_]u8 = [
+ 0xc1, 0xe5, 0x15, 0x76, 0x5b, 0xb2, 0x3a, 0xbc,
+ 0xc4, 0x71, 0xdf, 0xcc, 0x20, 0xe0, 0x63, 0xd6,
+ 0xb3, 0x7d, 0x48, 0x51, 0xe0, 0xd1, 0xcb, 0x07,
+ 0xa3, 0xc8, 0xc6, 0xb9, 0x43, 0xa9, 0x1e, 0x70,
+ 0xb2, 0x1e, 0xbe, 0xc3, 0x11, 0x36, 0xb2, 0x64,
+ 0x7c, 0xaf, 0x89, 0x46, 0x17, 0x60, 0x90, 0x19,
+ 0x23, 0x53, 0xd1, 0xce, 0xc6, 0x5c, 0x50, 0x9c,
+ 0x8c, 0x1d, 0xa8, 0xee, 0x44, 0x9c, 0xa2, 0xb2,
+ 0x97, 0x62, 0x39, 0xcc, 0x91, 0xb0, 0xcc, 0x2c,
+ 0x1b, 0x4c, 0xc3, 0x5d, 0xc3, 0xfa, 0xe9, 0x98,
+ ];
+
+ let result: [80]u8 = [0...];
+ let resultbuf = bufio::fixed(result, io::mode::WRITE);
+ let errw = errwriter(&resultbuf, 20, errors::again);
+ let buf: [CTR_BUFSIZE]u8 = [0...];
+
+ let b = ct64();
+ ct64_init(&b, key);
+ defer cipher::finish(&b);
- cipher::stream_xor(&ctr, result, result);
+ let ctr = cipher::ctr(&errw, &b, iv[..], buf[..]);
+ defer io::close(&ctr)!;
+ let n = 0z;
+ match (io::write(&ctr, plain[..64])) {
+ case errors::again =>
+ yield;
+ case size =>
+ assert(false);
+ };
+
+ errw.limit = 999;
+
+ // try again
+ n += io::write(&ctr, plain[..64])!;
+ n += io::write(&ctr, plain[64..])!;
+ assert(n == len(plain));
assert(bytes::equal(cipher, result));
};
diff --git a/crypto/cipher/ctr.ha b/crypto/cipher/ctr.ha
@@ -1,18 +1,23 @@
// License: MPL-2.0
-// (c) 2021 Armin Preiml <apreiml@strohwolke.at>
-// (c) 2021 Drew DeVault <sir@cmpwn.com>
+// (c) 2022 Armin Preiml <apreiml@strohwolke.at>
+// (c) 2022 Drew DeVault <sir@cmpwn.com>
use bytes;
use crypto::math;
+use crypto::math::{xor};
+use io;
+// A counter mode (CTR) stream.
export type ctr_stream = struct {
- stream,
+ xorstream,
b: *block,
counter: []u8,
xorbuf: []u8,
xorused: size,
};
-// Creates a counter mode stream (CTR).
+// Creates a counter mode (CTR) cipher stream which can be used for encryption
+// (by encrypting writes to the underlying handle) or decryption (or by
+// decrypting reads from the underlying handle), but not both.
//
// The user must supply an initialization vector (IV) equal in length to the
// block size of the underlying [[block]] cipher, and a temporary state buffer
@@ -20,9 +25,9 @@ export type ctr_stream = struct {
// underlying block cipher usually provides constants which define the lengths
// of these buffers for static allocation.
//
-// The user must call [[finish]] when they are done using the stream to
+// The user must call [[io::close]] when they are done using the stream to
// securely erase secret information stored in the stream state.
-export fn ctr(b: *block, iv: []u8, buf: []u8) ctr_stream = {
+export fn ctr(h: io::handle, b: *block, iv: []u8, buf: []u8) ctr_stream = {
assert(len(iv) == blocksz(b), "iv is of invalid block size");
assert(len(buf) >= blocksz(b) * 2, "buf must be at least 2 * blocksize");
@@ -41,41 +46,32 @@ export fn ctr(b: *block, iv: []u8, buf: []u8) ctr_stream = {
// cap the buffer to a multiple of bsz.
let maxxorbufsz = blocksz(b) * nparallel(b);
- let xorbufsz = len(xorbuf);
- if (xorbufsz < maxxorbufsz) {
- xorbufsz = xorbufsz - xorbufsz % blocksz(b);
+ const xorbufsz = if (len(xorbuf) < maxxorbufsz) {
+ yield len(xorbuf) - len(xorbuf) % blocksz(b);
} else {
- xorbufsz = maxxorbufsz;
+ yield maxxorbufsz;
};
- return ctr_stream {
- xor = &ctr_stream_xor,
+ let s = ctr_stream {
+ keybuf = &ctr_keybuf,
+ advance = &ctr_advance,
finish = &ctr_finish,
+
b = b,
counter = counter,
- xorbuf = xorbuf,
+ xorbuf = xorbuf[..xorbufsz],
// mark all as used to force fill xorbuf
xorused = xorbufsz,
+ ...
};
-};
-
-fn ctr_stream_xor(s: *stream, dest: []u8, src: []u8) void = {
- let ctr = s: *ctr_stream;
- for (let i = 0z; true) {
- for (ctr.xorused < len(ctr.xorbuf)) {
- dest[i] = src[i] ^ ctr.xorbuf[ctr.xorused];
- ctr.xorused += 1;
- i += 1;
- if (i >= len(dest)) {
- return;
- };
- };
- fill_xorbuf(ctr);
- };
+ xorstream_init(&s, h);
+ return s;
};
fn fill_xorbuf(ctr: *ctr_stream) void = {
const bsz = blocksz(ctr.b);
+
+ // Write and increment the counter to each available block
for (let i = 0z; i < len(ctr.xorbuf) / bsz; i += 1) {
ctr.xorbuf[i * bsz..(i * bsz + bsz)] = ctr.counter[0..bsz];
@@ -91,8 +87,26 @@ fn fill_xorbuf(ctr: *ctr_stream) void = {
ctr.xorused = 0;
};
-fn ctr_finish(s: *stream) void = {
+fn ctr_keybuf(s: *xorstream) []u8 = {
let ctr = s: *ctr_stream;
- bytes::zero(ctr.xorbuf);
+ if (ctr.xorused >= len(ctr.xorbuf)) {
+ fill_xorbuf(ctr);
+ };
+ return ctr.xorbuf[ctr.xorused..];
+};
+
+fn ctr_advance(s: *xorstream, n: size) void = {
+ let ctr = s: *ctr_stream;
+
+ // fill_xorbuf could be smarter, to skip multiple blocks at once.
+ // It's of no use, since xorstream doesn't support skipping an arbritary
+ // number of blocks.
+ assert(n <= len(ctr.xorbuf));
+
+ ctr.xorused += n;
};
+fn ctr_finish(s: *xorstream) void = {
+ let ctr = s: *ctr_stream;
+ bytes::zero(ctr.xorbuf);
+};
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -236,13 +236,15 @@ crypto_aes() {
if [ $testing -eq 0 ]
then
gensrcs_crypto_aes
- gen_ssa crypto::aes bytes crypto::cipher crypto::math endian rt
+ gen_ssa crypto::aes bytes crypto::cipher crypto::math endian \
+ rt io
else
gensrcs_crypto_aes \
ct64+test.ha \
cbc+test.ha \
ctr+test.ha
- gen_ssa crypto::aes bytes crypto::cipher crypto::math endian rt
+ gen_ssa crypto::aes bytes crypto::cipher crypto::math endian \
+ rt io bufio errors
fi
}
@@ -334,7 +336,7 @@ crypto_cipher() {
cbc.ha \
ctr.ha \
stream.ha
- gen_ssa crypto::cipher crypto::math bytes
+ gen_ssa crypto::cipher crypto::math bytes io
}
crypto_hmac() {
diff --git a/stdlib.mk b/stdlib.mk
@@ -760,7 +760,7 @@ $(HARECACHE)/crypto/crypto-any.ssa: $(stdlib_crypto_any_srcs) $(stdlib_rt) $(std
stdlib_crypto_aes_any_srcs = \
$(STDLIB)/crypto/aes/aes_ct64.ha
-$(HARECACHE)/crypto/aes/crypto_aes-any.ssa: $(stdlib_crypto_aes_any_srcs) $(stdlib_rt) $(stdlib_bytes_$(PLATFORM)) $(stdlib_crypto_cipher_$(PLATFORM)) $(stdlib_crypto_math_$(PLATFORM)) $(stdlib_endian_$(PLATFORM)) $(stdlib_rt_$(PLATFORM))
+$(HARECACHE)/crypto/aes/crypto_aes-any.ssa: $(stdlib_crypto_aes_any_srcs) $(stdlib_rt) $(stdlib_bytes_$(PLATFORM)) $(stdlib_crypto_cipher_$(PLATFORM)) $(stdlib_crypto_math_$(PLATFORM)) $(stdlib_endian_$(PLATFORM)) $(stdlib_rt_$(PLATFORM)) $(stdlib_io_$(PLATFORM))
@printf 'HAREC \t$@\n'
@mkdir -p $(HARECACHE)/crypto/aes
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::aes \
@@ -836,7 +836,7 @@ stdlib_crypto_cipher_any_srcs = \
$(STDLIB)/crypto/cipher/ctr.ha \
$(STDLIB)/crypto/cipher/stream.ha
-$(HARECACHE)/crypto/cipher/crypto_cipher-any.ssa: $(stdlib_crypto_cipher_any_srcs) $(stdlib_rt) $(stdlib_crypto_math_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM))
+$(HARECACHE)/crypto/cipher/crypto_cipher-any.ssa: $(stdlib_crypto_cipher_any_srcs) $(stdlib_rt) $(stdlib_crypto_math_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM)) $(stdlib_io_$(PLATFORM))
@printf 'HAREC \t$@\n'
@mkdir -p $(HARECACHE)/crypto/cipher
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::cipher \
@@ -2826,7 +2826,7 @@ testlib_crypto_aes_any_srcs = \
$(STDLIB)/crypto/aes/cbc+test.ha \
$(STDLIB)/crypto/aes/ctr+test.ha
-$(TESTCACHE)/crypto/aes/crypto_aes-any.ssa: $(testlib_crypto_aes_any_srcs) $(testlib_rt) $(testlib_bytes_$(PLATFORM)) $(testlib_crypto_cipher_$(PLATFORM)) $(testlib_crypto_math_$(PLATFORM)) $(testlib_endian_$(PLATFORM)) $(testlib_rt_$(PLATFORM))
+$(TESTCACHE)/crypto/aes/crypto_aes-any.ssa: $(testlib_crypto_aes_any_srcs) $(testlib_rt) $(testlib_bytes_$(PLATFORM)) $(testlib_crypto_cipher_$(PLATFORM)) $(testlib_crypto_math_$(PLATFORM)) $(testlib_endian_$(PLATFORM)) $(testlib_rt_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_bufio_$(PLATFORM)) $(testlib_errors_$(PLATFORM))
@printf 'HAREC \t$@\n'
@mkdir -p $(TESTCACHE)/crypto/aes
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncrypto::aes \
@@ -2909,7 +2909,7 @@ testlib_crypto_cipher_any_srcs = \
$(STDLIB)/crypto/cipher/ctr.ha \
$(STDLIB)/crypto/cipher/stream.ha
-$(TESTCACHE)/crypto/cipher/crypto_cipher-any.ssa: $(testlib_crypto_cipher_any_srcs) $(testlib_rt) $(testlib_crypto_math_$(PLATFORM)) $(testlib_bytes_$(PLATFORM))
+$(TESTCACHE)/crypto/cipher/crypto_cipher-any.ssa: $(testlib_crypto_cipher_any_srcs) $(testlib_rt) $(testlib_crypto_math_$(PLATFORM)) $(testlib_bytes_$(PLATFORM)) $(testlib_io_$(PLATFORM))
@printf 'HAREC \t$@\n'
@mkdir -p $(TESTCACHE)/crypto/cipher
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncrypto::cipher \