From fa8dd5a13b5c7e3101640d9bdcc3880436bd5114 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Mon, 4 Mar 2024 16:43:50 +0900 Subject: [PATCH] core/crypto: Misc cleanups and documentation improvements --- core/crypto/chacha20/chacha20.odin | 48 +++++++++++----- .../chacha20poly1305/chacha20poly1305.odin | 17 ++++++ core/crypto/hmac/hmac.odin | 2 +- core/crypto/poly1305/poly1305.odin | 55 +++++++++++++------ core/crypto/x25519/x25519.odin | 25 +++++++-- 5 files changed, 108 insertions(+), 39 deletions(-) diff --git a/core/crypto/chacha20/chacha20.odin b/core/crypto/chacha20/chacha20.odin index 43b3303c2..7f0950d03 100644 --- a/core/crypto/chacha20/chacha20.odin +++ b/core/crypto/chacha20/chacha20.odin @@ -1,11 +1,21 @@ +/* +package chacha20 implements the ChaCha20 and XChaCha20 stream ciphers. + +See: +- https://datatracker.ietf.org/doc/html/rfc8439 +- https://datatracker.ietf.org/doc/draft-irtf-cfrg-xchacha/03/ +*/ package chacha20 import "core:encoding/endian" import "core:math/bits" import "core:mem" +// KEY_SIZE is the (X)ChaCha20 key size in bytes. KEY_SIZE :: 32 +// NONCE_SIZE is the ChaCha20 nonce size in bytes. NONCE_SIZE :: 12 +// XNONCE_SIZE is the XChaCha20 nonce size in bytes. XNONCE_SIZE :: 24 @(private) @@ -19,25 +29,26 @@ _STATE_SIZE_U32 :: 16 _ROUNDS :: 20 @(private) -_SIGMA_0 : u32 : 0x61707865 +_SIGMA_0: u32 : 0x61707865 @(private) -_SIGMA_1 : u32 : 0x3320646e +_SIGMA_1: u32 : 0x3320646e @(private) -_SIGMA_2 : u32 : 0x79622d32 +_SIGMA_2: u32 : 0x79622d32 @(private) -_SIGMA_3 : u32 : 0x6b206574 +_SIGMA_3: u32 : 0x6b206574 +// Context is a ChaCha20 or XChaCha20 instance. Context :: struct { - _s: [_STATE_SIZE_U32]u32, - - _buffer: [_BLOCK_SIZE]byte, - _off: int, - + _s: [_STATE_SIZE_U32]u32, + _buffer: [_BLOCK_SIZE]byte, + _off: int, _is_ietf_flavor: bool, _is_initialized: bool, } -init :: proc (ctx: ^Context, key, nonce: []byte) { +// init inititializes a Context for ChaCha20 or XChaCha20 with the provided +// key and nonce. +init :: proc(ctx: ^Context, key, nonce: []byte) { if len(key) != KEY_SIZE { panic("crypto/chacha20: invalid ChaCha20 key size") } @@ -89,7 +100,8 @@ init :: proc (ctx: ^Context, key, nonce: []byte) { ctx._is_initialized = true } -seek :: proc (ctx: ^Context, block_nr: u64) { +// seek seeks the (X)ChaCha20 stream counter to the specified block. +seek :: proc(ctx: ^Context, block_nr: u64) { assert(ctx._is_initialized) if ctx._is_ietf_flavor { @@ -103,7 +115,10 @@ seek :: proc (ctx: ^Context, block_nr: u64) { ctx._off = _BLOCK_SIZE } -xor_bytes :: proc (ctx: ^Context, dst, src: []byte) { +// xor_bytes XORs each byte in src with bytes taken from the (X)ChaCha20 +// keystream, and writes the resulting output to dst. Dst and src MUST +// alias exactly or not at all. +xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { assert(ctx._is_initialized) // TODO: Enforcing that dst and src alias exactly or not at all @@ -147,7 +162,8 @@ xor_bytes :: proc (ctx: ^Context, dst, src: []byte) { } } -keystream_bytes :: proc (ctx: ^Context, dst: []byte) { +// keystream_bytes fills dst with the raw (X)ChaCha20 keystream output. +keystream_bytes :: proc(ctx: ^Context, dst: []byte) { assert(ctx._is_initialized) dst := dst @@ -180,7 +196,9 @@ keystream_bytes :: proc (ctx: ^Context, dst: []byte) { } } -reset :: proc (ctx: ^Context) { +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { mem.zero_explicit(&ctx._s, size_of(ctx._s)) mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer)) @@ -188,7 +206,7 @@ reset :: proc (ctx: ^Context) { } @(private) -_do_blocks :: proc (ctx: ^Context, dst, src: []byte, nr_blocks: int) { +_do_blocks :: proc(ctx: ^Context, dst, src: []byte, nr_blocks: int) { // Enforce the maximum consumed keystream per nonce. // // While all modern "standard" definitions of ChaCha20 use diff --git a/core/crypto/chacha20poly1305/chacha20poly1305.odin b/core/crypto/chacha20poly1305/chacha20poly1305.odin index 86fe54e79..7fc112d0d 100644 --- a/core/crypto/chacha20poly1305/chacha20poly1305.odin +++ b/core/crypto/chacha20poly1305/chacha20poly1305.odin @@ -1,3 +1,10 @@ +/* +package chacha20poly1305 implements the AEAD_CHACHA20_POLY1305 Authenticated +Encryption with Additional Data algorithm. + +See: +- https://www.rfc-editor.org/rfc/rfc8439 +*/ package chacha20poly1305 import "core:crypto" @@ -6,8 +13,11 @@ import "core:crypto/poly1305" import "core:encoding/endian" import "core:mem" +// KEY_SIZE is the chacha20poly1305 key size in bytes. KEY_SIZE :: chacha20.KEY_SIZE +// NONCE_SIZE is the chacha20poly1305 nonce size in bytes. NONCE_SIZE :: chacha20.NONCE_SIZE +// TAG_SIZE is the chacha20poly1305 tag size in bytes. TAG_SIZE :: poly1305.TAG_SIZE @(private) @@ -49,6 +59,8 @@ _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) if len(ciphertext) != len(plaintext) { @@ -95,6 +107,11 @@ encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) { 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. +// +// 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) if len(ciphertext) != len(plaintext) { diff --git a/core/crypto/hmac/hmac.odin b/core/crypto/hmac/hmac.odin index cd389fe6f..6aac8fca7 100644 --- a/core/crypto/hmac/hmac.odin +++ b/core/crypto/hmac/hmac.odin @@ -11,7 +11,7 @@ import "core:crypto/hash" import "core:mem" // sum will compute the HMAC with the specified algorithm and key -// over msg, and write the computed digest to dst. It requires that +// over msg, and write the computed tag to dst. It requires that // the dst buffer is the tag size. sum :: proc(algorithm: hash.Algorithm, dst, msg, key: []byte) { ctx: Context diff --git a/core/crypto/poly1305/poly1305.odin b/core/crypto/poly1305/poly1305.odin index a2fb3c223..fa57c6c06 100644 --- a/core/crypto/poly1305/poly1305.odin +++ b/core/crypto/poly1305/poly1305.odin @@ -1,3 +1,9 @@ +/* +package poly1305 implements the Poly1305 one-time MAC algorithm. + +See: +- https://datatracker.ietf.org/doc/html/rfc8439 +*/ package poly1305 import "core:crypto" @@ -5,13 +11,20 @@ import field "core:crypto/_fiat/field_poly1305" import "core:encoding/endian" import "core:mem" +// KEY_SIZE is the Poly1305 key size in bytes. KEY_SIZE :: 32 +// TAG_SIZE is the Poly1305 tag size in bytes. TAG_SIZE :: 16 @(private) _BLOCK_SIZE :: 16 -sum :: proc (dst, msg, key: []byte) { +// sum will compute the Poly1305 MAC with the key over msg, and write +// the computed tag to dst. It requires that the dst buffer is the tag +// size. +// +// The key SHOULD be unique and MUST be unpredictable for each invocation. +sum :: proc(dst, msg, key: []byte) { ctx: Context = --- init(&ctx, key) @@ -19,9 +32,12 @@ sum :: proc (dst, msg, key: []byte) { final(&ctx, dst) } -verify :: proc (tag, msg, key: []byte) -> bool { +// verify will verify the Poly1305 tag computed with the key over msg and +// return true iff the tag is valid. It requires that the tag is correctly +// sized. +verify :: proc(tag, msg, key: []byte) -> bool { ctx: Context = --- - derived_tag: [16]byte = --- + derived_tag: [TAG_SIZE]byte = --- init(&ctx, key) update(&ctx, msg) @@ -30,18 +46,19 @@ verify :: proc (tag, msg, key: []byte) -> bool { return crypto.compare_constant_time(derived_tag[:], tag) == 1 } +// Context is a Poly1305 instance. Context :: struct { - _r: field.Tight_Field_Element, - _a: field.Tight_Field_Element, - _s: field.Tight_Field_Element, - - _buffer: [_BLOCK_SIZE]byte, - _leftover: int, - + _r: field.Tight_Field_Element, + _a: field.Tight_Field_Element, + _s: field.Tight_Field_Element, + _buffer: [_BLOCK_SIZE]byte, + _leftover: int, _is_initialized: bool, } -init :: proc (ctx: ^Context, key: []byte) { +// init initializes a Context with the specified key. The key SHOULD be +// unique and MUST be unpredictable for each invocation. +init :: proc(ctx: ^Context, key: []byte) { if len(key) != KEY_SIZE { panic("crypto/poly1305: invalid key size") } @@ -64,7 +81,8 @@ init :: proc (ctx: ^Context, key: []byte) { ctx._is_initialized = true } -update :: proc (ctx: ^Context, data: []byte) { +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { assert(ctx._is_initialized) msg := data @@ -101,8 +119,11 @@ update :: proc (ctx: ^Context, data: []byte) { } } -final :: proc (ctx: ^Context, dst: []byte) { +// final finalizes the Context, writes the tag to dst, and calls +// reset on the Context. +final :: proc(ctx: ^Context, dst: []byte) { assert(ctx._is_initialized) + defer reset(ctx) if len(dst) != TAG_SIZE { panic("poly1305: invalid destination tag size") @@ -125,11 +146,11 @@ final :: proc (ctx: ^Context, dst: []byte) { tmp: [32]byte = --- field.fe_to_bytes(&tmp, &ctx._a) copy_slice(dst, tmp[0:16]) - - reset(ctx) } -reset :: proc (ctx: ^Context) { +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { mem.zero_explicit(&ctx._r, size_of(ctx._r)) mem.zero_explicit(&ctx._a, size_of(ctx._a)) mem.zero_explicit(&ctx._s, size_of(ctx._s)) @@ -139,7 +160,7 @@ reset :: proc (ctx: ^Context) { } @(private) -_blocks :: proc (ctx: ^Context, msg: []byte, final := false) { +_blocks :: proc(ctx: ^Context, msg: []byte, final := false) { n: field.Tight_Field_Element = --- final_byte := byte(!final) diff --git a/core/crypto/x25519/x25519.odin b/core/crypto/x25519/x25519.odin index fc446d25c..285666a32 100644 --- a/core/crypto/x25519/x25519.odin +++ b/core/crypto/x25519/x25519.odin @@ -1,9 +1,18 @@ +/* +package x25519 implements the X25519 (aka curve25519) Elliptic-Curve +Diffie-Hellman key exchange protocol. + +See: +- https://www.rfc-editor.org/rfc/rfc7748 +*/ package x25519 import field "core:crypto/_fiat/field_curve25519" import "core:mem" +// SCALAR_SIZE is the size of a X25519 scalar (private key) in bytes. SCALAR_SIZE :: 32 +// POINT_SIZE is the size of a X25519 point (public key/shared secret) in bytes. POINT_SIZE :: 32 @(private) @@ -14,11 +23,11 @@ _scalar_bit :: #force_inline proc "contextless" (s: ^[32]byte, i: int) -> u8 { if i < 0 { return 0 } - return (s[i>>3] >> uint(i&7)) & 1 + return (s[i >> 3] >> uint(i & 7)) & 1 } @(private) -_scalarmult :: proc (out, scalar, point: ^[32]byte) { +_scalarmult :: proc(out, scalar, point: ^[32]byte) { // Montgomery pseduo-multiplication taken from Monocypher. // computes the scalar product @@ -26,7 +35,7 @@ _scalarmult :: proc (out, scalar, point: ^[32]byte) { field.fe_from_bytes(&x1, point) // computes the actual scalar product (the result is in x2 and z2) - x2, x3, z2, z3: field.Tight_Field_Element = ---, ---, ---, --- + x2, x3, z2, z3: field.Tight_Field_Element = ---, ---, ---, --- t0, t1: field.Loose_Field_Element = ---, --- // Montgomery ladder @@ -38,7 +47,7 @@ _scalarmult :: proc (out, scalar, point: ^[32]byte) { field.fe_one(&z3) swap: int - for pos := 255-1; pos >= 0; pos = pos - 1 { + for pos := 255 - 1; pos >= 0; pos = pos - 1 { // constant time conditional swap before ladder step b := int(_scalar_bit(scalar, pos)) swap ~= b // xor trick avoids swapping at the end of the loop @@ -94,7 +103,9 @@ _scalarmult :: proc (out, scalar, point: ^[32]byte) { mem.zero_explicit(&t1, size_of(t1)) } -scalarmult :: proc (dst, scalar, point: []byte) { +// scalarmult "multiplies" the provided scalar and point, and writes the +// resulting point to dst. +scalarmult :: proc(dst, scalar, point: []byte) { if len(scalar) != SCALAR_SIZE { panic("crypto/x25519: invalid scalar size") } @@ -123,7 +134,9 @@ scalarmult :: proc (dst, scalar, point: []byte) { mem.zero_explicit(&d, size_of(d)) } -scalarmult_basepoint :: proc (dst, scalar: []byte) { +// scalarmult_basepoint "multiplies" the provided scalar with the X25519 +// base point and writes the resulting point to dst. +scalarmult_basepoint :: proc(dst, scalar: []byte) { // TODO/perf: Switch to using a precomputed table. scalarmult(dst, scalar, _BASE_POINT[:]) }