mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-05 15:18:49 +00:00
core/crypto/chacha20poly1305: Support AEAD_XChaCha20_Poly1305
IETF-draft flavor (32-bit counter) though this makes no practical difference.
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
package chacha20poly1305 implements the AEAD_CHACHA20_POLY1305 Authenticated
|
package chacha20poly1305 implements the AEAD_CHACHA20_POLY1305 and
|
||||||
Encryption with Additional Data algorithm.
|
AEAD_XChaCha20_Poly1305 Authenticated Encryption with Additional Data
|
||||||
|
algorithms.
|
||||||
|
|
||||||
See:
|
See:
|
||||||
- https://www.rfc-editor.org/rfc/rfc8439
|
- https://www.rfc-editor.org/rfc/rfc8439
|
||||||
|
- https://datatracker.ietf.org/doc/html/draft-arciszewski-xchacha-03
|
||||||
*/
|
*/
|
||||||
package chacha20poly1305
|
package chacha20poly1305
|
||||||
|
|
||||||
@@ -17,6 +19,8 @@ import "core:mem"
|
|||||||
KEY_SIZE :: chacha20.KEY_SIZE
|
KEY_SIZE :: chacha20.KEY_SIZE
|
||||||
// NONCE_SIZE is the chacha20poly1305 nonce size in bytes.
|
// NONCE_SIZE is the chacha20poly1305 nonce size in bytes.
|
||||||
NONCE_SIZE :: chacha20.NONCE_SIZE
|
NONCE_SIZE :: chacha20.NONCE_SIZE
|
||||||
|
// XNONCE_SIZE is the xchacha20poly1305 nonce size in bytes.
|
||||||
|
XNONCE_SIZE :: chacha20.XNONCE_SIZE
|
||||||
// TAG_SIZE is the chacha20poly1305 tag size in bytes.
|
// TAG_SIZE is the chacha20poly1305 tag size in bytes.
|
||||||
TAG_SIZE :: poly1305.TAG_SIZE
|
TAG_SIZE :: poly1305.TAG_SIZE
|
||||||
|
|
||||||
@@ -24,11 +28,12 @@ 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, nonce, aad, text: []byte) {
|
_validate_common_slice_sizes :: proc (tag, nonce, aad, text: []byte, is_xchacha: bool) {
|
||||||
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(nonce) != NONCE_SIZE {
|
expected_nonce_len := is_xchacha ? XNONCE_SIZE : NONCE_SIZE
|
||||||
|
if len(nonce) != expected_nonce_len {
|
||||||
panic("crypto/chacha20poly1305: invalid nonce size")
|
panic("crypto/chacha20poly1305: invalid nonce size")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,14 +61,15 @@ _update_mac_pad16 :: #force_inline proc (ctx: ^poly1305.Context, x_len: int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context is a keyed Chacha20Poly1305 instance.
|
// Context is a keyed (X)Chacha20Poly1305 instance.
|
||||||
Context :: struct {
|
Context :: struct {
|
||||||
_key: [KEY_SIZE]byte,
|
_key: [KEY_SIZE]byte,
|
||||||
_impl: chacha20.Implementation,
|
_impl: chacha20.Implementation,
|
||||||
|
_is_xchacha: bool,
|
||||||
_is_initialized: bool,
|
_is_initialized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// init initializes a Context with the provided key.
|
// init initializes a Context with the provided key, for AEAD_CHACHA20_POLY1305.
|
||||||
init :: proc(ctx: ^Context, key: []byte, impl := chacha20.Implementation.Simd256) {
|
init :: proc(ctx: ^Context, key: []byte, impl := chacha20.Implementation.Simd256) {
|
||||||
if len(key) != KEY_SIZE {
|
if len(key) != KEY_SIZE {
|
||||||
panic("crypto/chacha20poly1305: invalid key size")
|
panic("crypto/chacha20poly1305: invalid key size")
|
||||||
@@ -71,22 +77,34 @@ init :: proc(ctx: ^Context, key: []byte, impl := chacha20.Implementation.Simd256
|
|||||||
|
|
||||||
copy(ctx._key[:], key)
|
copy(ctx._key[:], key)
|
||||||
ctx._impl = impl
|
ctx._impl = impl
|
||||||
|
ctx._is_xchacha = false
|
||||||
ctx._is_initialized = true
|
ctx._is_initialized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// init_xchacha initializes a Context with the provided key, for
|
||||||
|
// AEAD_XChaCha20_Poly1305.
|
||||||
|
//
|
||||||
|
// Note: While there are multiple definitions of XChaCha20-Poly1305
|
||||||
|
// this sticks to the IETF draft and uses a 32-bit counter.
|
||||||
|
init_xchacha :: proc(ctx: ^Context, key: []byte, impl := chacha20.Implementation.Simd256) {
|
||||||
|
init(ctx, key, impl)
|
||||||
|
ctx._is_xchacha = true
|
||||||
|
}
|
||||||
|
|
||||||
// seal encrypts the plaintext and authenticates the aad and ciphertext,
|
// seal encrypts the plaintext and authenticates the aad and ciphertext,
|
||||||
// with the provided Context and nonce, stores the output in dst and tag.
|
// with the provided Context and nonce, stores the output in dst and tag.
|
||||||
//
|
//
|
||||||
// dst and plaintext MUST alias exactly or not at all.
|
// dst and plaintext MUST alias exactly or not at all.
|
||||||
seal :: proc(ctx: ^Context, dst, tag, nonce, aad, plaintext: []byte) {
|
seal :: proc(ctx: ^Context, dst, tag, nonce, aad, plaintext: []byte) {
|
||||||
ciphertext := dst
|
ciphertext := dst
|
||||||
_validate_common_slice_sizes(tag, nonce, aad, plaintext)
|
_validate_common_slice_sizes(tag, nonce, aad, plaintext, ctx._is_xchacha)
|
||||||
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, ctx._key[:], nonce, ctx._impl)
|
chacha20.init(&stream_ctx, ctx._key[:], nonce, ctx._impl)
|
||||||
|
stream_ctx._state._is_ietf_flavor = true
|
||||||
|
|
||||||
// otk = poly1305_key_gen(key, nonce)
|
// otk = poly1305_key_gen(key, nonce)
|
||||||
otk: [poly1305.KEY_SIZE]byte = ---
|
otk: [poly1305.KEY_SIZE]byte = ---
|
||||||
@@ -133,7 +151,7 @@ seal :: proc(ctx: ^Context, dst, tag, nonce, aad, plaintext: []byte) {
|
|||||||
// dst and plaintext MUST alias exactly or not at all.
|
// dst and plaintext MUST alias exactly or not at all.
|
||||||
open :: proc(ctx: ^Context, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
|
open :: proc(ctx: ^Context, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
|
||||||
plaintext := dst
|
plaintext := dst
|
||||||
_validate_common_slice_sizes(tag, nonce, aad, ciphertext)
|
_validate_common_slice_sizes(tag, nonce, aad, ciphertext, ctx._is_xchacha)
|
||||||
if len(ciphertext) != len(plaintext) {
|
if len(ciphertext) != len(plaintext) {
|
||||||
panic("crypto/chacha20poly1305: invalid destination plaintext size")
|
panic("crypto/chacha20poly1305: invalid destination plaintext size")
|
||||||
}
|
}
|
||||||
@@ -144,6 +162,7 @@ open :: proc(ctx: ^Context, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
|
|||||||
|
|
||||||
stream_ctx: chacha20.Context = ---
|
stream_ctx: chacha20.Context = ---
|
||||||
chacha20.init(&stream_ctx, ctx._key[:], nonce, ctx._impl)
|
chacha20.init(&stream_ctx, ctx._key[:], nonce, ctx._impl)
|
||||||
|
stream_ctx._state._is_ietf_flavor = true
|
||||||
|
|
||||||
// otk = poly1305_key_gen(key, nonce)
|
// otk = poly1305_key_gen(key, nonce)
|
||||||
otk: [poly1305.KEY_SIZE]byte = ---
|
otk: [poly1305.KEY_SIZE]byte = ---
|
||||||
@@ -191,5 +210,6 @@ open :: proc(ctx: ^Context, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
|
|||||||
// re-initialized to be used again.
|
// re-initialized to be used again.
|
||||||
reset :: proc "contextless" (ctx: ^Context) {
|
reset :: proc "contextless" (ctx: ^Context) {
|
||||||
mem.zero_explicit(&ctx._key, len(ctx._key))
|
mem.zero_explicit(&ctx._key, len(ctx._key))
|
||||||
|
ctx._is_xchacha = false
|
||||||
ctx._is_initialized = false
|
ctx._is_initialized = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ 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, impl)
|
||||||
|
test_xchacha20poly1305(t, impl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,6 +284,67 @@ test_chacha20poly1305 :: proc(t: ^testing.T, impl: chacha20.Implementation) {
|
|||||||
testing.expectf(t, !ok, "chacha20poly1305/%v: Expected false for open(tag, bad_aad, ciphertext)", impl)
|
testing.expectf(t, !ok, "chacha20poly1305/%v: Expected false for open(tag, bad_aad, ciphertext)", impl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_xchacha20poly1305 :: proc(t: ^testing.T, impl: chacha20.Implementation) {
|
||||||
|
// Test case taken from:
|
||||||
|
// - https://datatracker.ietf.org/doc/html/draft-arciszewski-xchacha-03
|
||||||
|
key_str := "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"
|
||||||
|
iv_str := "404142434445464748494a4b4c4d4e4f5051525354555657"
|
||||||
|
aad_str := "50515253c0c1c2c3c4c5c6c7"
|
||||||
|
plaintext_str := "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e"
|
||||||
|
ciphertext_str := "bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b4522f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff921f9664c97637da9768812f615c68b13b52e"
|
||||||
|
tag_str := "c0875924c1c7987947deafd8780acf49"
|
||||||
|
|
||||||
|
key, _ := hex.decode(transmute([]byte)(key_str), context.temp_allocator)
|
||||||
|
iv, _ := hex.decode(transmute([]byte)(iv_str), context.temp_allocator)
|
||||||
|
aad, _ := hex.decode(transmute([]byte)(aad_str), context.temp_allocator)
|
||||||
|
plaintext, _ := hex.decode(transmute([]byte)(plaintext_str), context.temp_allocator)
|
||||||
|
ciphertext, _ := hex.decode(transmute([]byte)(ciphertext_str), context.temp_allocator)
|
||||||
|
tag, _ := hex.decode(transmute([]byte)(tag_str), context.temp_allocator)
|
||||||
|
|
||||||
|
tag_ := make([]byte, len(tag), context.temp_allocator)
|
||||||
|
dst := make([]byte, len(ciphertext), context.temp_allocator)
|
||||||
|
|
||||||
|
ctx: chacha20poly1305.Context
|
||||||
|
chacha20poly1305.init_xchacha(&ctx, key, impl)
|
||||||
|
|
||||||
|
chacha20poly1305.seal(&ctx, dst, tag_, iv, aad, plaintext)
|
||||||
|
dst_str := string(hex.encode(dst, context.temp_allocator))
|
||||||
|
tag_str_ := string(hex.encode(tag_, context.temp_allocator))
|
||||||
|
|
||||||
|
testing.expectf(
|
||||||
|
t,
|
||||||
|
dst_str == ciphertext_str && tag_str_ == tag_str,
|
||||||
|
"xchacha20poly1305/%v: Expected: (%s, %s) for seal(%s, %s, %s, %s), but got (%s, %s) instead",
|
||||||
|
impl,
|
||||||
|
ciphertext_str,
|
||||||
|
tag_str,
|
||||||
|
key_str,
|
||||||
|
iv_str,
|
||||||
|
aad_str,
|
||||||
|
plaintext_str,
|
||||||
|
dst_str,
|
||||||
|
tag_str_,
|
||||||
|
)
|
||||||
|
|
||||||
|
ok := chacha20poly1305.open(&ctx, dst, iv, aad, ciphertext, tag)
|
||||||
|
dst_str = string(hex.encode(dst, context.temp_allocator))
|
||||||
|
|
||||||
|
testing.expectf(
|
||||||
|
t,
|
||||||
|
ok && dst_str == plaintext_str,
|
||||||
|
"xchacha20poly1305/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %v) instead",
|
||||||
|
impl,
|
||||||
|
plaintext_str,
|
||||||
|
key_str,
|
||||||
|
iv_str,
|
||||||
|
aad_str,
|
||||||
|
ciphertext_str,
|
||||||
|
tag_str,
|
||||||
|
dst_str,
|
||||||
|
ok,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_rand_bytes :: proc(t: ^testing.T) {
|
test_rand_bytes :: proc(t: ^testing.T) {
|
||||||
if !crypto.HAS_RAND_BYTES {
|
if !crypto.HAS_RAND_BYTES {
|
||||||
|
|||||||
Reference in New Issue
Block a user