Skip to content

Commit

Permalink
core/crypto/aegis: Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
Yawning committed Sep 10, 2024
1 parent 5f8be67 commit 4198575
Show file tree
Hide file tree
Showing 7 changed files with 930 additions and 2 deletions.
47 changes: 45 additions & 2 deletions core/crypto/aead/low_level.odin
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package aead

import "core:crypto/aegis"
import "core:crypto/aes"
import "core:crypto/chacha20"
import "core:crypto/chacha20poly1305"
Expand All @@ -15,7 +16,7 @@ Implementation :: union {

// MAX_TAG_SIZE is the maximum size tag that can be returned by any of the
// Algorithms supported via this package.
MAX_TAG_SIZE :: 16
MAX_TAG_SIZE :: 32

// Algorithm is the algorithm identifier associated with a given Context.
Algorithm :: enum {
Expand All @@ -25,16 +26,24 @@ Algorithm :: enum {
AES_GCM_256,
CHACHA20POLY1305,
XCHACHA20POLY1305,
AEGIS_128L,
AEGIS_128L_256, // AEGIS-128L (256-bit tag)
AEGIS_256,
AEGIS_256_256, // AEGIS-256 (256-bit tag)
}

// ALGORITM_NAMES is the Agorithm to algorithm name string.
// ALGORITM_NAMES is the Algorithm to algorithm name string.
ALGORITHM_NAMES := [Algorithm]string {
.Invalid = "Invalid",
.AES_GCM_128 = "AES-GCM-128",
.AES_GCM_192 = "AES-GCM-192",
.AES_GCM_256 = "AES-GCM-256",
.CHACHA20POLY1305 = "chacha20poly1305",
.XCHACHA20POLY1305 = "xchacha20poly1305",
.AEGIS_128L = "AEGIS-128L",
.AEGIS_128L_256 = "AEGIS-128L-256",
.AEGIS_256 = "AEGIS-256",
.AEGIS_256_256 = "AEGIS-256-256",
}

// TAG_SIZES is the Algorithm to tag size in bytes.
Expand All @@ -45,6 +54,10 @@ TAG_SIZES := [Algorithm]int {
.AES_GCM_256 = aes.GCM_TAG_SIZE,
.CHACHA20POLY1305 = chacha20poly1305.TAG_SIZE,
.XCHACHA20POLY1305 = chacha20poly1305.TAG_SIZE,
.AEGIS_128L = aegis.TAG_SIZE_128,
.AEGIS_128L_256 = aegis.TAG_SIZE_256,
.AEGIS_256 = aegis.TAG_SIZE_128,
.AEGIS_256_256 = aegis.TAG_SIZE_256,
}

// KEY_SIZES is the Algorithm to key size in bytes.
Expand All @@ -55,6 +68,10 @@ KEY_SIZES := [Algorithm]int {
.AES_GCM_256 = aes.KEY_SIZE_256,
.CHACHA20POLY1305 = chacha20poly1305.KEY_SIZE,
.XCHACHA20POLY1305 = chacha20poly1305.KEY_SIZE,
.AEGIS_128L = aegis.KEY_SIZE_128L,
.AEGIS_128L_256 = aegis.KEY_SIZE_128L,
.AEGIS_256 = aegis.KEY_SIZE_256,
.AEGIS_256_256 = aegis.KEY_SIZE_256,
}

// IV_SIZES is the Algorithm to initialization vector size in bytes.
Expand All @@ -67,6 +84,10 @@ IV_SIZES := [Algorithm]int {
.AES_GCM_256 = aes.GCM_IV_SIZE,
.CHACHA20POLY1305 = chacha20poly1305.IV_SIZE,
.XCHACHA20POLY1305 = chacha20poly1305.XIV_SIZE,
.AEGIS_128L = aegis.IV_SIZE_128L,
.AEGIS_128L_256 = aegis.IV_SIZE_128L,
.AEGIS_256 = aegis.IV_SIZE_256,
.AEGIS_256_256 = aegis.IV_SIZE_256,
}

// Context is a concrete instantiation of a specific AEAD algorithm.
Expand All @@ -75,6 +96,7 @@ Context :: struct {
_impl: union {
aes.Context_GCM,
chacha20poly1305.Context,
aegis.Context,
},
}

Expand All @@ -86,6 +108,10 @@ _IMPL_IDS := [Algorithm]typeid {
.AES_GCM_256 = typeid_of(aes.Context_GCM),
.CHACHA20POLY1305 = typeid_of(chacha20poly1305.Context),
.XCHACHA20POLY1305 = typeid_of(chacha20poly1305.Context),
.AEGIS_128L = typeid_of(aegis.Context),
.AEGIS_128L_256 = typeid_of(aegis.Context),
.AEGIS_256 = typeid_of(aegis.Context),
.AEGIS_256_256 = typeid_of(aegis.Context),
}

// init initializes a Context with a specific AEAD Algorithm.
Expand Down Expand Up @@ -113,6 +139,9 @@ init :: proc(ctx: ^Context, algorithm: Algorithm, key: []byte, impl: Implementat
case .XCHACHA20POLY1305:
impl_ := impl != nil ? impl.(chacha20.Implementation) : chacha20.DEFAULT_IMPLEMENTATION
chacha20poly1305.init_xchacha(&ctx._impl.(chacha20poly1305.Context), key, impl_)
case .AEGIS_128L, .AEGIS_128L_256, .AEGIS_256, .AEGIS_256_256:
impl_ := impl != nil ? impl.(aes.Implementation) : aes.DEFAULT_IMPLEMENTATION
aegis.init(&ctx._impl.(aegis.Context), key, impl_)
case .Invalid:
panic("crypto/aead: uninitialized algorithm")
case:
Expand All @@ -127,11 +156,17 @@ init :: proc(ctx: ^Context, algorithm: Algorithm, key: []byte, impl: Implementat
//
// dst and plaintext MUST alias exactly or not at all.
seal_ctx :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) {
if len(tag) != TAG_SIZES[ctx._algo] {
panic("crypto/aead: invalid tag size")
}

switch &impl in ctx._impl {
case aes.Context_GCM:
aes.seal_gcm(&impl, dst, tag, iv, aad, plaintext)
case chacha20poly1305.Context:
chacha20poly1305.seal(&impl, dst, tag, iv, aad, plaintext)
case aegis.Context:
aegis.seal(&impl, dst, tag, iv, aad, plaintext)
case:
panic("crypto/aead: uninitialized algorithm")
}
Expand All @@ -145,11 +180,17 @@ seal_ctx :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) {
// dst and plaintext MUST alias exactly or not at all.
@(require_results)
open_ctx :: proc(ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool {
if len(tag) != TAG_SIZES[ctx._algo] {
panic("crypto/aead: invalid tag size")
}

switch &impl in ctx._impl {
case aes.Context_GCM:
return aes.open_gcm(&impl, dst, iv, aad, ciphertext, tag)
case chacha20poly1305.Context:
return chacha20poly1305.open(&impl, dst, iv, aad, ciphertext, tag)
case aegis.Context:
return aegis.open(&impl, dst, iv, aad, ciphertext, tag)
case:
panic("crypto/aead: uninitialized algorithm")
}
Expand All @@ -163,6 +204,8 @@ reset :: proc(ctx: ^Context) {
aes.reset_gcm(&impl)
case chacha20poly1305.Context:
chacha20poly1305.reset(&impl)
case aegis.Context:
aegis.reset(&impl)
case:
// Calling reset repeatedly is fine.
}
Expand Down
188 changes: 188 additions & 0 deletions core/crypto/aegis/aegis.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
package aegis implements the AEGIS-128L and AEGIS-256 Authenticated
Encryption with Additional Data algorithms.
See:
- [[ https://www.ietf.org/archive/id/draft-irtf-cfrg-aegis-aead-11.txt ]]
*/
package aegis

import "core:bytes"
import "core:crypto"
import "core:crypto/aes"
import "core:mem"

// KEY_SIZE_128L is the AEGIS-128L key size in bytes.
KEY_SIZE_128L :: 16
// KEY_SIZE_256 is the AEGIS-256 key size in bytes.
KEY_SIZE_256 :: 32
// IV_SIZE_128L is the AEGIS-128L IV size in bytes.
IV_SIZE_128L :: 16
// IV_SIZE_256 is the AEGIS-256 IV size in bytes.
IV_SIZE_256 :: 32
// TAG_SIZE_128 is the AEGIS-128L or AEGIS-256 128-bit tag size in bytes.
TAG_SIZE_128 :: 16
// TAG_SIZE_256 is the AEGIS-128L or AEGIS-256 256-bit tag size in bytes.
TAG_SIZE_256 :: 32

@(private)
_RATE_128L :: 32
@(private)
_RATE_256 :: 16
@(private)
_RATE_MAX :: _RATE_128L

@(private, rodata)
_C0 := [16]byte{
0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d,
0x15, 0x22, 0x37, 0x59, 0x90, 0xe9, 0x79, 0x62,
}

@(private, rodata)
_C1 := [16]byte {
0xdb, 0x3d, 0x18, 0x55, 0x6d, 0xc2, 0x2f, 0xf1,
0x20, 0x11, 0x31, 0x42, 0x73, 0xb5, 0x28, 0xdd,
}

// Context is a keyed AEGIS-128L or AEGIS-256 instance.
Context :: struct {
_key: [KEY_SIZE_256]byte,
_key_len: int,
_impl: aes.Implementation,
_is_initialized: bool,
}

@(private)
_validate_common_slice_sizes :: proc (ctx: ^Context, tag, iv, aad, text: []byte) {
switch len(tag) {
case TAG_SIZE_128, TAG_SIZE_256:
case:
panic("crypto/aegis: invalid destination tag size")
}

iv_ok: bool
switch ctx._key_len {
case KEY_SIZE_128L:
iv_ok = len(iv) == IV_SIZE_128L
case KEY_SIZE_256:
iv_ok = len(iv) == IV_SIZE_256
}
if !iv_ok {
panic("crypto/aegis: invalid IV size")
}

#assert(size_of(int) == 8 || size_of(int) <= 4)
// As A_MAX and P_MAX are both defined to be 2^61 - 1 bytes, and
// the maximum length of a slice is bound by `size_of(int)`, where
// `int` is register sized, there is no need to check AAD/text
// lengths/
}

// init initializes a Context with the provided key, for AEGIS-128L or AEGIS-256.
init :: proc(ctx: ^Context, key: []byte, impl := aes.DEFAULT_IMPLEMENTATION) {
switch len(key) {
case KEY_SIZE_128L, KEY_SIZE_256:
case:
panic("crypto/aegis: invalid key size")
}

copy(ctx._key[:], key)
ctx._key_len = len(key)
ctx._impl = impl
if ctx._impl == .Hardware && !is_hardware_accelerated() {
ctx._impl = .Portable
}
ctx._is_initialized = true
}

// seal encrypts the plaintext and authenticates the aad and ciphertext,
// with the provided Context and iv, stores the output in dst and tag.
//
// dst and plaintext MUST alias exactly or not at all.
seal :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) {
assert(ctx._is_initialized)

_validate_common_slice_sizes(ctx, tag, iv, aad, plaintext)
if len(dst) != len(plaintext) {
panic("crypto/aegis: invalid destination ciphertext size")
}
if bytes.alias_inexactly(dst, plaintext) {
panic("crypto/aegis: dst and plaintext alias inexactly")
}

if ctx._impl == .Hardware {
st: State_HW
defer reset_state_hw(&st)

init_hw(ctx, &st, iv)

aad_len, pt_len := len(aad), len(plaintext)
if aad_len > 0 {
absorb_hw(&st, aad)
}

if pt_len > 0 {
enc_hw(&st, dst, plaintext)
}

finalize_hw(&st, tag, aad_len, pt_len)
} else {
panic("core/crypto/aegis: not implemented")
}
}

// open authenticates the aad and ciphertext, and decrypts the ciphertext,
// with the provided Context, iv, and tag, and stores the output in dst,
// returning true iff the authentication was successful. If authentication
// fails, the destination buffer will be zeroed.
//
// dst and plaintext MUST alias exactly or not at all.
@(require_results)
open :: proc(ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool {
assert(ctx._is_initialized)

_validate_common_slice_sizes(ctx, tag, iv, aad, ciphertext)
if len(dst) != len(ciphertext) {
panic("crypto/aegis: invalid destination plaintext size")
}
if bytes.alias_inexactly(dst, ciphertext) {
panic("crypto/aegis: dst and ciphertext alias inexactly")
}

if ctx._impl == .Hardware {
st: State_HW
defer reset_state_hw(&st)

init_hw(ctx, &st, iv)

aad_len, ct_len := len(aad), len(ciphertext)
if aad_len > 0 {
absorb_hw(&st, aad)
}

if ct_len > 0 {
dec_hw(&st, dst, ciphertext)
}

tmp: [TAG_SIZE_256]byte
derived_tag := tmp[:len(tag)]
finalize_hw(&st, derived_tag, aad_len, ct_len)

if crypto.compare_constant_time(tag, derived_tag) != 1 {
mem.zero_explicit(raw_data(dst), ct_len)
return false
}
} else {
panic("core/crypto/aegis: not implemented")
}

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._key_len = 0
ctx._is_initialized = false
}
44 changes: 44 additions & 0 deletions core/crypto/aegis/aegis_impl_hw_gen.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//+build !amd64
package aegis

@(private = "file")
ERR_HW_NOT_SUPPORTED :: "crypto/aegis: hardware implementation unsupported"

@(private)
State_HW :: struct {}

// is_hardware_accelerated returns true iff hardware accelerated AEGIS
// is supported.
is_hardware_accelerated :: proc "contextless" () -> bool {
return false
}

@(private)
init_hw :: proc "contextless" (ctx: ^Context, st: ^State_HW, iv: []byte) {
panic_contextless(ERR_HW_NOT_SUPPORTED)
}

@(private)
absorb_hw :: proc "contextless" (st: ^State_HW, aad: []byte) {
panic_contextless(ERR_HW_NOT_SUPPORTED)
}

@(private)
enc_hw :: proc "contextless" (st: ^State_HW, dst, src: []byte) {
panic_contextless(ERR_HW_NOT_SUPPORTED)
}

@(private)
dec_hw :: proc "contextless" (st: ^State_HW, dst, src: []byte) {
panic_contextless(ERR_HW_NOT_SUPPORTED)
}

@(private)
finalize_hw :: proc "contextless" (st: ^State_HW, tag: []byte, ad_len, msg_len: int) {
panic_contextless(ERR_HW_NOT_SUPPORTED)
}

@(private)
reset_state_hw :: proc "contextless" (st: ^State_HW) {
panic_contextless(ERR_HW_NOT_SUPPORTED)
}
Loading

0 comments on commit 4198575

Please sign in to comment.