Browse Source

core/crypto/chacha20poly1305: Change the interface to match GCM

Yawning Angel 1 year ago
parent
commit
8efc98ce90

+ 43 - 16
core/crypto/chacha20poly1305/chacha20poly1305.odin

@@ -24,13 +24,10 @@ TAG_SIZE :: poly1305.TAG_SIZE
 _P_MAX :: 64 * 0xffffffff // 64 * (2^32-1)
 _P_MAX :: 64 * 0xffffffff // 64 * (2^32-1)
 
 
 @(private)
 @(private)
-_validate_common_slice_sizes :: proc (tag, key, nonce, aad, text: []byte) {
+_validate_common_slice_sizes :: proc (tag, nonce, aad, text: []byte) {
 	if len(tag) != TAG_SIZE {
 	if len(tag) != TAG_SIZE {
 		panic("crypto/chacha20poly1305: invalid destination tag size")
 		panic("crypto/chacha20poly1305: invalid destination tag size")
 	}
 	}
-	if len(key) != KEY_SIZE {
-		panic("crypto/chacha20poly1305: invalid key size")
-	}
 	if len(nonce) != NONCE_SIZE {
 	if len(nonce) != NONCE_SIZE {
 		panic("crypto/chacha20poly1305: invalid nonce size")
 		panic("crypto/chacha20poly1305: invalid nonce size")
 	}
 	}
@@ -59,16 +56,37 @@ _update_mac_pad16 :: #force_inline proc (ctx: ^poly1305.Context, x_len: int) {
 	}
 	}
 }
 }
 
 
-// encrypt encrypts the plaintext and authenticates the aad and ciphertext,
-// with the provided key and nonce, stores the output in ciphertext and tag.
-encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) {
-	_validate_common_slice_sizes(tag, key, nonce, aad, plaintext)
+// Context is a keyed Chacha20Poly1305 instance.
+Context :: struct {
+	_key:  [KEY_SIZE]byte,
+	_impl: chacha20.Implementation,
+	_is_initialized: bool,
+}
+
+// init initializes a Context with the provided key.
+init :: proc(ctx: ^Context, key: []byte, impl := chacha20.Implementation.Simd256) {
+	if len(key) != KEY_SIZE {
+		panic("crypto/chacha20poly1305: invalid key size")
+	}
+
+	copy(ctx._key[:], key)
+	ctx._impl = impl
+	ctx._is_initialized = true
+}
+
+// seal encrypts the plaintext and authenticates the aad and ciphertext,
+// with the provided Context and nonce, stores the output in dst and tag.
+//
+// dst and plaintext MUST alias exactly or not at all.
+seal :: proc(ctx: ^Context, dst, tag, nonce, aad, plaintext: []byte) {
+	ciphertext := dst
+	_validate_common_slice_sizes(tag, nonce, aad, plaintext)
 	if len(ciphertext) != len(plaintext) {
 	if len(ciphertext) != len(plaintext) {
 		panic("crypto/chacha20poly1305: invalid destination ciphertext size")
 		panic("crypto/chacha20poly1305: invalid destination ciphertext size")
 	}
 	}
 
 
 	stream_ctx: chacha20.Context = ---
 	stream_ctx: chacha20.Context = ---
-	chacha20.init(&stream_ctx, key, nonce)
+	chacha20.init(&stream_ctx, ctx._key[:], nonce, ctx._impl)
 
 
 	// otk = poly1305_key_gen(key, nonce)
 	// otk = poly1305_key_gen(key, nonce)
 	otk: [poly1305.KEY_SIZE]byte = ---
 	otk: [poly1305.KEY_SIZE]byte = ---
@@ -107,13 +125,15 @@ encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) {
 	poly1305.final(&mac_ctx, tag) // Implicitly sanitizes context.
 	poly1305.final(&mac_ctx, tag) // Implicitly sanitizes context.
 }
 }
 
 
-// decrypt authenticates the aad and ciphertext, and decrypts the ciphertext,
-// with the provided key, nonce, and tag, and stores the output in plaintext,
-// returning true iff the authentication was successful.
+// open authenticates the aad and ciphertext, and decrypts the ciphertext,
+// with the provided Context, nonce, and tag, and stores the output in dst,
+// returning true iff the authentication was successful.  If authentication
+// fails, the destination buffer will be zeroed.
 //
 //
-// If authentication fails, the destination plaintext buffer will be zeroed.
-decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool {
-	_validate_common_slice_sizes(tag, key, nonce, aad, ciphertext)
+// dst and plaintext MUST alias exactly or not at all.
+open :: proc(ctx: ^Context, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
+	plaintext := dst
+	_validate_common_slice_sizes(tag, nonce, aad, ciphertext)
 	if len(ciphertext) != len(plaintext) {
 	if len(ciphertext) != len(plaintext) {
 		panic("crypto/chacha20poly1305: invalid destination plaintext size")
 		panic("crypto/chacha20poly1305: invalid destination plaintext size")
 	}
 	}
@@ -123,7 +143,7 @@ decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool {
 	// points where needed.
 	// points where needed.
 
 
 	stream_ctx: chacha20.Context = ---
 	stream_ctx: chacha20.Context = ---
-	chacha20.init(&stream_ctx, key, nonce)
+	chacha20.init(&stream_ctx, ctx._key[:], nonce, ctx._impl)
 
 
 	// otk = poly1305_key_gen(key, nonce)
 	// otk = poly1305_key_gen(key, nonce)
 	otk: [poly1305.KEY_SIZE]byte = ---
 	otk: [poly1305.KEY_SIZE]byte = ---
@@ -166,3 +186,10 @@ decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool {
 
 
 	return true
 	return true
 }
 }
+
+// reset sanitizes the Context.  The Context must be
+// re-initialized to be used again.
+reset :: proc "contextless" (ctx: ^Context) {
+	mem.zero_explicit(&ctx._key, len(ctx._key))
+	ctx._is_initialized = false
+}

+ 4 - 1
tests/benchmark/crypto/benchmark_crypto.odin

@@ -339,10 +339,13 @@ _benchmark_chacha20poly1305 :: proc(
 		0x00, 0x00, 0x00, 0x00,
 		0x00, 0x00, 0x00, 0x00,
 	}
 	}
 
 
+	ctx: chacha20poly1305.Context = ---
+	chacha20poly1305.init(&ctx, key[:]) // Basically 0 overhead.
+
 	tag: [chacha20poly1305.TAG_SIZE]byte = ---
 	tag: [chacha20poly1305.TAG_SIZE]byte = ---
 
 
 	for _ in 0 ..= options.rounds {
 	for _ in 0 ..= options.rounds {
-		chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf)
+		chacha20poly1305.seal(&ctx, buf, tag[:], nonce[:], nil, buf)
 	}
 	}
 	options.count = options.rounds
 	options.count = options.rounds
 	options.processed = options.rounds * options.bytes
 	options.processed = options.rounds * options.bytes

+ 50 - 60
tests/core/crypto/test_core_crypto.odin

@@ -44,8 +44,8 @@ test_chacha20 :: proc(t: ^testing.T) {
 
 
 	for impl in impls {
 	for impl in impls {
 		test_chacha20_stream(t, impl)
 		test_chacha20_stream(t, impl)
+		test_chacha20poly1305(t, impl)
 	}
 	}
-	test_chacha20poly1305(t) // TODO: Move into loop.
 }
 }
 
 
 test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) {
 test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) {
@@ -93,7 +93,8 @@ test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) {
 	testing.expectf(
 	testing.expectf(
 		t,
 		t,
 		derived_ciphertext_str == ciphertext_str,
 		derived_ciphertext_str == ciphertext_str,
-		"Expected %s for xor_bytes(plaintext_str), but got %s instead",
+		"chacha20/%v: Expected %s for xor_bytes(plaintext_str), but got %s instead",
+		impl,
 		ciphertext_str,
 		ciphertext_str,
 		derived_ciphertext_str,
 		derived_ciphertext_str,
 	)
 	)
@@ -138,7 +139,8 @@ test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) {
 	testing.expectf(
 	testing.expectf(
 		t,
 		t,
 		derived_ciphertext_str == xciphertext_str,
 		derived_ciphertext_str == xciphertext_str,
-		"Expected %s for xor_bytes(plaintext_str), but got %s instead",
+		"chacha20/%v: Expected %s for xor_bytes(plaintext_str), but got %s instead",
+		impl,
 		xciphertext_str,
 		xciphertext_str,
 		derived_ciphertext_str,
 		derived_ciphertext_str,
 	)
 	)
@@ -170,14 +172,16 @@ test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) {
 	testing.expectf(
 	testing.expectf(
 		t,
 		t,
 		expected_digest_str == digest_str,
 		expected_digest_str == digest_str,
-		"Expected %s for keystream digest, but got %s instead",
+		"chacha20/%v: Expected %s for keystream digest, but got %s instead",
+		impl,
 		expected_digest_str,
 		expected_digest_str,
 		digest_str,
 		digest_str,
 	)
 	)
 }
 }
 
 
-test_chacha20poly1305 :: proc(t: ^testing.T) {
+test_chacha20poly1305 :: proc(t: ^testing.T, impl: chacha20.Implementation) {
 	plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR)
 	plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR)
+	plaintext_str := string(hex.encode(plaintext, context.temp_allocator))
 
 
 	aad := [12]byte {
 	aad := [12]byte {
 		0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3,
 		0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3,
@@ -221,76 +225,62 @@ test_chacha20poly1305 :: proc(t: ^testing.T) {
 	}
 	}
 	tag_str := string(hex.encode(tag[:], context.temp_allocator))
 	tag_str := string(hex.encode(tag[:], context.temp_allocator))
 
 
-	derived_tag: [chacha20poly1305.TAG_SIZE]byte
-	derived_ciphertext: [114]byte
+	tag_ := make([]byte, chacha20poly1305.TAG_SIZE, context.temp_allocator)
+	dst := make([]byte, len(ciphertext), context.temp_allocator)
 
 
-	chacha20poly1305.encrypt(
-		derived_ciphertext[:],
-		derived_tag[:],
-		key[:],
-		nonce[:],
-		aad[:],
-		plaintext,
-	)
+	ctx: chacha20poly1305.Context
+	chacha20poly1305.init(&ctx, key[:])
+
+	chacha20poly1305.seal(&ctx, dst, tag_, nonce[:], aad[:], plaintext)
+	dst_str := string(hex.encode(dst, context.temp_allocator))
+	tag_str_ := string(hex.encode(tag_, context.temp_allocator))
 
 
-	derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator))
 	testing.expectf(
 	testing.expectf(
 		t,
 		t,
-		derived_ciphertext_str == ciphertext_str,
-		"Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead",
+		dst_str == ciphertext_str && tag_str_ == tag_str,
+		"chacha20poly1305/%v: Expected: (%s, %s) for seal(%x, %x, %x, %x), but got (%s, %s) instead",
+		impl,
 		ciphertext_str,
 		ciphertext_str,
-		derived_ciphertext_str,
+		tag_str,
+		key,
+		nonce,
+		aad,
+		plaintext,
+		dst_str,
+		tag_str_,
 	)
 	)
 
 
-	derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator))
+	ok := chacha20poly1305.open(&ctx, dst, nonce[:], aad[:], ciphertext[:], tag[:])
+	dst_str = string(hex.encode(dst, context.temp_allocator))
+
 	testing.expectf(
 	testing.expectf(
 		t,
 		t,
-		derived_tag_str == tag_str,
-		"Expected tag %s for encrypt(aad, plaintext), but got %s instead",
+		ok && dst_str == plaintext_str,
+		"chacha20poly1305/%v: Expected: (%s, true) for open(%x, %x, %x, %x, %s), but got (%s, %v) instead",
+		impl,
+		plaintext_str,
+		key,
+		nonce,
+		aad,
+		ciphertext,
 		tag_str,
 		tag_str,
-		derived_tag_str,
+		dst_str,
+		ok,
 	)
 	)
 
 
-	derived_plaintext: [114]byte
-	ok := chacha20poly1305.decrypt(
-		derived_plaintext[:],
-		tag[:],
-		key[:],
-		nonce[:],
-		aad[:],
-		ciphertext[:],
-	)
-	derived_plaintext_str := string(derived_plaintext[:])
-	testing.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)")
-	testing.expectf(
-		t,
-		derived_plaintext_str == _PLAINTEXT_SUNSCREEN_STR,
-		"Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead",
-		_PLAINTEXT_SUNSCREEN_STR,
-		derived_plaintext_str,
-	)
+	copy(dst, ciphertext[:])
+	tag_[0] ~= 0xa5
+	ok = chacha20poly1305.open(&ctx, dst, nonce[:], aad[:], dst[:], tag_)
+	testing.expectf(t, !ok, "chacha20poly1305/%v: Expected false for open(bad_tag, aad, ciphertext)", impl)
 
 
-	derived_ciphertext[0] ~= 0xa5
-	ok = chacha20poly1305.decrypt(
-		derived_plaintext[:],
-		tag[:],
-		key[:],
-		nonce[:],
-		aad[:],
-		derived_ciphertext[:],
-	)
-	testing.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)")
+	dst[0] ~= 0xa5
+	ok = chacha20poly1305.open(&ctx, dst, nonce[:], aad[:], dst[:], tag[:])
+	testing.expectf(t, !ok, "chacha20poly1305/%v: Expected false for open(tag, aad, bad_ciphertext)", impl)
 
 
+	copy(dst, ciphertext[:])
 	aad[0] ~= 0xa5
 	aad[0] ~= 0xa5
-	ok = chacha20poly1305.decrypt(
-		derived_plaintext[:],
-		tag[:],
-		key[:],
-		nonce[:],
-		aad[:],
-		ciphertext[:],
-	)
-	testing.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)")
+	ok = chacha20poly1305.open(&ctx, dst, nonce[:], aad[:], dst[:], tag[:])
+	testing.expectf(t, !ok, "chacha20poly1305/%v: Expected false for open(tag, bad_aad, ciphertext)", impl)
 }
 }
 
 
 @(test)
 @(test)