commit f0fcc878219cc034a9f110f6a2b36912db148238
parent fd6a3ec9675a5d98759f41d3c1d17f3e4752134f
Author: Drew DeVault <sir@cmpwn.com>
Date: Tue, 23 Nov 2021 12:31:33 +0100
crypto: address secure erasure of secret data
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
11 files changed, 135 insertions(+), 37 deletions(-)
diff --git a/bytes/zero.ha b/bytes/zero.ha
@@ -0,0 +1,7 @@
+// Sets all bytes in a slice to zero. This is suitable for erasing private data
+// from a slice.
+export fn zero(buf: []u8) void = {
+ for (let i = 0z; i < len(buf); i += 1) {
+ buf[i] = 0;
+ };
+};
diff --git a/crypto/aes/aes_ct64.ha b/crypto/aes/aes_ct64.ha
@@ -46,21 +46,25 @@ export type ct64_block = struct {
// Returns an AES [[crypto::cipher::block]] cipher implementation optimized for
// constant time operation on 64-bit systems.
-export fn ct64(key: []u8) ct64_block = {
- let state = ct64_block {
- blocksz = BLOCKSIZE,
- nparallel = CT64_NPARALLEL,
- encrypt = &aes_ct64_encrypt,
- decrypt = &aes_ct64_decrypt,
- ...
- };
+//
+// The caller must call [[ct64_init]] to add a key to the cipher before using
+// the cipher, and must call [[crypto::cipher::finish]] when they are finished
+// using the cipher to securely erase any secret data stored in the cipher
+// state.
+export fn ct64() ct64_block = ct64_block {
+ blocksz = BLOCKSIZE,
+ nparallel = CT64_NPARALLEL,
+ encrypt = &aes_ct64_encrypt,
+ decrypt = &aes_ct64_decrypt,
+ finish = &aes_ct64_finish,
+ ...
+};
+// Initializes the ct64 AES implementation with an encryption key.
+export fn ct64_init(cipher: *ct64_block, key: []u8) void = {
let comp_skey: [30]u64 = [0...];
- state.rounds = br_aes_ct64_keysched(comp_skey[..], key, len(key));
-
- br_aes_ct64_skey_expand(state.sk_exp, state.rounds, comp_skey[..]);
-
- return state;
+ cipher.rounds = br_aes_ct64_keysched(comp_skey[..], key, len(key));
+ br_aes_ct64_skey_expand(cipher.sk_exp, cipher.rounds, comp_skey[..]);
};
// Combines up to 4 blocks and encrypts them in one run
@@ -118,6 +122,11 @@ fn aes_ct64_decrypt(block: *cipher::block, dest: []u8, src: []u8) void = {
br_range_enc32le(dest, w);
};
+fn aes_ct64_finish(block: *cipher::block) void = {
+ let b = block: *ct64_block;
+ b.sk_exp = [0...];
+};
+
// see br_aes_ct64_ortho in src/inner.h of BearSSL
fn br_aes_ct64_ortho(q: []u64) void = {
swapn(0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 1, &q[0], &q[1]);
diff --git a/crypto/aes/cbc+test.ha b/crypto/aes/cbc+test.ha
@@ -37,7 +37,10 @@ use bytes;
let result: [64]u8 = [0...];
let buf: [CBC_BUFSIZE]u8 = [0...];
- let b = ct64(key);
+ let b = ct64();
+ ct64_init(&b, key);
+ defer cipher::finish(&b);
+
let cbc = cipher::cbc_encryptor(&b, iv[..], buf[..]);
cipher::cbc_encrypt(&cbc, result, plain);
@@ -85,7 +88,10 @@ use bytes;
let result: [64]u8 = plain;
let buf: [CBC_BUFSIZE]u8 = [0...];
- let b = ct64(key);
+ let b = ct64();
+ ct64_init(&b, key);
+ defer cipher::finish(&b);
+
let cbc = cipher::cbc_encryptor(&b, iv[..], buf[..]);
cipher::cbc_encrypt(&cbc, result, result);
diff --git a/crypto/aes/ct64+test.ha b/crypto/aes/ct64+test.ha
@@ -19,7 +19,9 @@ use bytes;
let result: [16]u8 = [0...];
- let block = ct64(key[..]);
+ let block = ct64();
+ ct64_init(&block, key);
+ defer cipher::finish(&block);
cipher::encrypt(&block, result[..], plain[..]);
assert(bytes::equal(cipher, result));
@@ -54,7 +56,9 @@ use bytes;
];
- let block = ct64(key[..]);
+ let block = ct64();
+ ct64_init(&block, key[..]);
+ defer cipher::finish(&block);
// test from 1 to 4 parallel blocks
for (let i = 0z; i < 4; i += 1) {
@@ -96,7 +100,9 @@ use bytes;
];
- let block = ct64(key[..]);
+ let block = ct64();
+ ct64_init(&block, key[..]);
+ defer cipher::finish(&block);
// test from 1 to 4 parallel blocks
for (let i = 0z; i < 4; i += 1) {
@@ -127,7 +133,10 @@ use bytes;
let result: [16]u8 = [0...];
- let block = ct64(key[..]);
+ let block = ct64();
+ ct64_init(&block, key[..]);
+ defer cipher::finish(&block);
+
cipher::decrypt(&block, result[..], cipher[..]);
assert(bytes::equal(plain, result));
@@ -152,7 +161,9 @@ use bytes;
let result: [16]u8 = [0...];
- let block = ct64(key[..]);
+ let block = ct64();
+ ct64_init(&block, key[..]);
+ defer cipher::finish(&block);
cipher::encrypt(&block, result[..], plain[..]);
assert(bytes::equal(cipher, result));
@@ -179,7 +190,9 @@ use bytes;
let result: [16]u8 = plain;
- let block = ct64(key[..]);
+ let block = ct64();
+ ct64_init(&block, key[..]);
+ defer cipher::finish(&block);
cipher::encrypt(&block, result[..], result[..]);
assert(bytes::equal(cipher, result));
@@ -207,7 +220,9 @@ use bytes;
];
let result: [16]u8 = [0...];
- let block = ct64(key[..]);
+ let block = ct64();
+ ct64_init(&block, key[..]);
+ defer cipher::finish(&block);
cipher::encrypt(&block, result[..], plain[..]);
assert(bytes::equal(cipher, result));
@@ -236,7 +251,9 @@ use bytes;
];
let result: [16]u8 = [0...];
- let block = ct64(key[..]);
+ let block = ct64();
+ ct64_init(&block, key[..]);
+ defer cipher::finish(&block);
cipher::encrypt(&block, result[..], plain[..]);
assert(bytes::equal(cipher, result));
diff --git a/crypto/aes/ctr+test.ha b/crypto/aes/ctr+test.ha
@@ -25,7 +25,10 @@ use bytes;
let result: [16]u8 = [0...];
let buf: [CTR_BUFSIZE]u8 = [0...];
- let b = ct64(key);
+ let b = ct64();
+ ct64_init(&b, key);
+ defer cipher::finish(&b);
+
let ctr = cipher::ctr(&b, iv[..], buf[..]);
cipher::stream_xor(&ctr, result, plain);
@@ -58,7 +61,10 @@ use bytes;
let result: [18]u8 = [0...];
let buf: [CTR_BUFSIZE]u8 = [0...];
- let b = ct64(key);
+ let b = ct64();
+ ct64_init(&b, key);
+ defer cipher::finish(&b);
+
let ctr = cipher::ctr(&b, iv[..], buf[..]);
cipher::stream_xor(&ctr, result, plain);
@@ -105,7 +111,10 @@ use bytes;
let result: [80]u8 = [0...];
let buf: [CTR_BUFSIZE]u8 = [0...];
- let b = ct64(key);
+ let b = ct64();
+ ct64_init(&b, key);
+ defer cipher::finish(&b);
+
let ctr = cipher::ctr(&b, iv[..], buf[..]);
cipher::stream_xor(&ctr, result, plain);
@@ -152,7 +161,9 @@ use bytes;
let result: [80]u8 = [0...];
let buf: [CTR_BUFSIZE]u8 = [0...];
- let b = ct64(key);
+ let b = ct64();
+ ct64_init(&b, key);
+ defer cipher::finish(&b);
let ctr = cipher::ctr(&b, iv[..], buf[..]);
cipher::stream_xor(&ctr, result[0..32], plain[0..32]);
@@ -189,7 +200,10 @@ use bytes;
0xb1, 0xa6, 0xd9, 0x40, 0xaf, 0x1b, 0x02, 0xe1,
];
- let b = ct64(key);
+ let b = ct64();
+ ct64_init(&b, key);
+ defer cipher::finish(&b);
+
let buf: [CTR_BUFSIZE]u8 = [0...];
let ctr = cipher::ctr(&b, iv[..], buf[..]);
@@ -220,7 +234,10 @@ use bytes;
0xb1, 0xa6, 0xd9, 0x40, 0xaf, 0x1b, 0x02, 0xe1,
];
- let b = ct64(key);
+ let b = ct64();
+ ct64_init(&b, key);
+ defer cipher::finish(&b);
+
let buf: [64]u8 = [0...];
let ctr = cipher::ctr(&b, iv[..], buf[..]);
diff --git a/crypto/cipher/block.ha b/crypto/cipher/block.ha
@@ -6,6 +6,7 @@ export type block = struct {
encrypt: *fn(b: *block, dest: []u8, src: []u8) void,
decrypt: *fn(b: *block, dest: []u8, src: []u8) void,
+ finish: *fn(b: *block) void,
};
// Returns the block size in bytes for a [[block]] cipher.
@@ -22,3 +23,7 @@ export fn encrypt(b: *block, dest: []u8, src: []u8) void =
// Decrypt up to [[nparallel]] blocks from 'src' and writes to 'dest'.
export fn decrypt(b: *block, dest: []u8, src: []u8) void =
b.decrypt(b, dest, src);
+
+// Discards any state associated with a block cipher algorithm, securely erasing
+// secret data from memory.
+export fn finish(b: *block) void = b.finish(b);
diff --git a/crypto/cipher/ctr.ha b/crypto/cipher/ctr.ha
@@ -1,3 +1,4 @@
+use bytes;
use crypto::math;
export type ctr_stream = struct {
@@ -15,6 +16,9 @@ export type ctr_stream = struct {
// whose size is equal to the block size times two. The module providing the
// underlying block cipher usually provides constants which define the lengths
// of these buffers for static allocation.
+//
+// The user must call [[ctr_finish]] 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 = {
assert(len(iv) == blocksz(b), "iv is of invalid block size");
assert(len(buf) >= blocksz(b) * 2, "buf must be at least 2 * blocksize");
@@ -51,6 +55,12 @@ export fn ctr(b: *block, iv: []u8, buf: []u8) ctr_stream = {
};
};
+// Discards state associated with a CTR stream, securely erasing secret data
+// from memory.
+export fn ctr_finish(ctr: *ctr_stream) void = {
+ bytes::zero(ctr.xorbuf);
+};
+
fn ctr_stream_xor(s: *stream, dest: []u8, src: []u8) void = {
let ctr = s: *ctr_stream;
const bsz = blocksz(ctr.b);
diff --git a/crypto/conventions.txt b/crypto/conventions.txt
@@ -0,0 +1,17 @@
+This is a WIP document offering some advice on how to implement cryptographic
+algorithms securely for Hare.
+
+All cryptographic algorithms must be constant time, such that an attacker cannot
+learn any secret information by analysis of the time required to complete a
+cryptographic operation. Not all of the math performed by cryptographic
+algorithms in Hare needs to be constant-time: just math whose inputs include
+secret information.
+
+It is important to know that secret data has been securely erased from memory
+when it is no longer required. A few items to note about Hare:
+
+- Return-by-value will leave garbage on the stack which is copied into the
+ caller's stack frame and abandoned. You cannot return-by-value any objects
+ which contain secret information.
+- Assignments are never optimized away. You can securely erase an array's
+ contents with array = [0...];
diff --git a/crypto/hmac/hmac.ha b/crypto/hmac/hmac.ha
@@ -1,3 +1,4 @@
+use bytes;
use crypto::math;
use hash;
use io;
@@ -13,6 +14,10 @@ export type state = struct {
// Creates a [[hash::hash]] that computes an HMAC using the provided hash
// creation function, e.g. [[crypto::sha256::create]].
+//
+// The caller must take extra care to call [[hash::close]] when they are
+// finished using the hash, which, in addition to freeing state associated with
+// the hash, will securely erase state which contains secret information.
export fn hmac(create: *hash::createfunc, key: []u8) state = {
let hinner = create(), houter = create();
let bsz = hash::bsz(hinner);
@@ -57,6 +62,8 @@ export fn hmac(create: *hash::createfunc, key: []u8) state = {
fn close(s: *io::stream) void = {
let h: *state = s: *state;
+ bytes::zero(h.okeypad);
+ bytes::zero(h.ikeypad);
free(h.okeypad);
free(h.ikeypad);
free(h.sumbuf);
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -164,7 +164,8 @@ bytes() {
index.ha \
reverse.ha \
tokenize.ha \
- two_way.ha
+ two_way.ha \
+ zero.ha
gen_ssa bytes types
}
@@ -228,7 +229,7 @@ crypto_cipher() {
cbc.ha \
ctr.ha \
stream.ha
- gen_ssa crypto::cipher crypto::math
+ gen_ssa crypto::cipher crypto::math bytes
}
crypto_hmac() {
@@ -236,7 +237,7 @@ crypto_hmac() {
then
gen_srcs crypto::hmac \
hmac.ha
- gen_ssa crypto::hmac crypto::math hash io
+ gen_ssa crypto::hmac crypto::math hash io bytes
else
gen_srcs crypto::hmac \
hmac.ha \
diff --git a/stdlib.mk b/stdlib.mk
@@ -628,7 +628,8 @@ stdlib_bytes_any_srcs= \
$(STDLIB)/bytes/index.ha \
$(STDLIB)/bytes/reverse.ha \
$(STDLIB)/bytes/tokenize.ha \
- $(STDLIB)/bytes/two_way.ha
+ $(STDLIB)/bytes/two_way.ha \
+ $(STDLIB)/bytes/zero.ha
$(HARECACHE)/bytes/bytes-any.ssa: $(stdlib_bytes_any_srcs) $(stdlib_rt) $(stdlib_types_$(PLATFORM))
@printf 'HAREC \t$@\n'
@@ -683,7 +684,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))
+$(HARECACHE)/crypto/cipher/crypto_cipher-any.ssa: $(stdlib_crypto_cipher_any_srcs) $(stdlib_rt) $(stdlib_crypto_math_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM))
@printf 'HAREC \t$@\n'
@mkdir -p $(HARECACHE)/crypto/cipher
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::cipher \
@@ -693,7 +694,7 @@ $(HARECACHE)/crypto/cipher/crypto_cipher-any.ssa: $(stdlib_crypto_cipher_any_src
stdlib_crypto_hmac_any_srcs= \
$(STDLIB)/crypto/hmac/hmac.ha
-$(HARECACHE)/crypto/hmac/crypto_hmac-any.ssa: $(stdlib_crypto_hmac_any_srcs) $(stdlib_rt) $(stdlib_crypto_math_$(PLATFORM)) $(stdlib_hash_$(PLATFORM)) $(stdlib_io_$(PLATFORM))
+$(HARECACHE)/crypto/hmac/crypto_hmac-any.ssa: $(stdlib_crypto_hmac_any_srcs) $(stdlib_rt) $(stdlib_crypto_math_$(PLATFORM)) $(stdlib_hash_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM))
@printf 'HAREC \t$@\n'
@mkdir -p $(HARECACHE)/crypto/hmac
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::hmac \
@@ -2346,7 +2347,8 @@ testlib_bytes_any_srcs= \
$(STDLIB)/bytes/index.ha \
$(STDLIB)/bytes/reverse.ha \
$(STDLIB)/bytes/tokenize.ha \
- $(STDLIB)/bytes/two_way.ha
+ $(STDLIB)/bytes/two_way.ha \
+ $(STDLIB)/bytes/zero.ha
$(TESTCACHE)/bytes/bytes-any.ssa: $(testlib_bytes_any_srcs) $(testlib_rt) $(testlib_types_$(PLATFORM))
@printf 'HAREC \t$@\n'
@@ -2407,7 +2409,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))
+$(TESTCACHE)/crypto/cipher/crypto_cipher-any.ssa: $(testlib_crypto_cipher_any_srcs) $(testlib_rt) $(testlib_crypto_math_$(PLATFORM)) $(testlib_bytes_$(PLATFORM))
@printf 'HAREC \t$@\n'
@mkdir -p $(TESTCACHE)/crypto/cipher
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncrypto::cipher \