mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 10:31:50 +00:00
Merge pull request #941 from boolean-false/crypto-library
Crypto library
This commit is contained in:
commit
ef4bd4f594
17 changed files with 2800 additions and 7 deletions
4
.github/workflows/appimage.yml
vendored
4
.github/workflows/appimage.yml
vendored
|
|
@ -31,7 +31,7 @@ jobs:
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev libglew2.2 \
|
sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev libglew2.2 \
|
||||||
libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev \
|
libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev \
|
||||||
libcurl4-openssl-dev libgtest-dev libfreetype6-dev cmake squashfs-tools valgrind
|
libcurl4-openssl-dev libssl-dev libgtest-dev libfreetype6-dev cmake squashfs-tools valgrind
|
||||||
# fix luajit paths
|
# fix luajit paths
|
||||||
sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a
|
sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a
|
||||||
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
||||||
|
|
@ -46,7 +46,7 @@ jobs:
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build -t install
|
run: cmake --build build -t install
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: ctest --test-dir build
|
run: ctest --output-on-failure --test-dir build
|
||||||
- name: Run engine tests
|
- name: Run engine tests
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
2
.github/workflows/cmake.yml
vendored
2
.github/workflows/cmake.yml
vendored
|
|
@ -39,7 +39,7 @@ jobs:
|
||||||
# make && make install INSTALL_INC=/usr/include/lua
|
# make && make install INSTALL_INC=/usr/include/lua
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libgtest-dev libcurl4-openssl-dev libfreetype6-dev
|
sudo apt-get install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libgtest-dev libcurl4-openssl-dev libssl-dev libfreetype6-dev
|
||||||
# fix luajit paths
|
# fix luajit paths
|
||||||
sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua-5.1.a
|
sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua-5.1.a
|
||||||
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||||
libvorbis-dev \
|
libvorbis-dev \
|
||||||
libcurl4-openssl-dev \
|
libcurl4-openssl-dev \
|
||||||
libfreetype6-dev \
|
libfreetype6-dev \
|
||||||
|
libssl-dev \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
wget \
|
wget \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
|
||||||
41
dev/tests/crypto.lua
Normal file
41
dev/tests/crypto.lua
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
assert(crypto.API_VERSION == 1)
|
||||||
|
local features = crypto.features()
|
||||||
|
assert(features.api_version == 1)
|
||||||
|
assert(features.backend == "OpenSSL")
|
||||||
|
|
||||||
|
local binary = "a\0b"
|
||||||
|
assert(crypto.hash("SHA256", binary) == crypto.sha256(binary))
|
||||||
|
local stream = crypto.hash_new("SHA256")
|
||||||
|
assert(stream:update("a"):update("\0b"):final() == crypto.sha256(binary))
|
||||||
|
stream:reset():update("abc")
|
||||||
|
assert(stream:final() == crypto.sha256("abc"))
|
||||||
|
|
||||||
|
local private, public = crypto.ed25519_keypair()
|
||||||
|
assert(crypto.ed25519_public(private) == public)
|
||||||
|
local signature = crypto.ed25519_sign(private, binary)
|
||||||
|
assert(crypto.ed25519_verify(public, binary, signature) == true)
|
||||||
|
local ok, error_code = crypto.ed25519_verify(public, "changed", signature)
|
||||||
|
assert(ok == false and error_code == "invalid_signature")
|
||||||
|
|
||||||
|
local x_private_a, x_public_a = crypto.x25519_keypair()
|
||||||
|
local x_private_b, x_public_b = crypto.x25519_keypair()
|
||||||
|
assert(crypto.x25519(x_private_a, x_public_b) ==
|
||||||
|
crypto.x25519_shared(x_private_b, x_public_a))
|
||||||
|
|
||||||
|
local p_private_a, p_public_a = crypto.p256_keypair()
|
||||||
|
local p_private_b, p_public_b = crypto.p256_keypair()
|
||||||
|
assert(crypto.p256_public(p_private_a) == p_public_a)
|
||||||
|
assert(crypto.p256_shared(p_private_a, p_public_b) ==
|
||||||
|
crypto.p256_shared(p_private_b, p_public_a))
|
||||||
|
|
||||||
|
local key = crypto.random_bytes(16)
|
||||||
|
local nonce = crypto.random_bytes(12)
|
||||||
|
local ciphertext = crypto.aes_gcm_encrypt(key, nonce, "aad", binary)
|
||||||
|
assert(crypto.aes_gcm_decrypt(key, nonce, "aad", ciphertext) == binary)
|
||||||
|
local plaintext, decrypt_error = crypto.aes_gcm_decrypt(
|
||||||
|
key, nonce, "changed", ciphertext
|
||||||
|
)
|
||||||
|
assert(plaintext == nil and decrypt_error == "authentication_failed")
|
||||||
|
|
||||||
|
assert(#crypto.pbkdf2("SHA256", "password", "salt", 1, 32) == 32)
|
||||||
|
assert(#crypto.scrypt("password", "salt", 16, 1, 1, 32) == 32)
|
||||||
|
|
@ -14,7 +14,8 @@ Subsections:
|
||||||
- [base64](scripting/builtins/libbase64.md)
|
- [base64](scripting/builtins/libbase64.md)
|
||||||
- [bjson, json, toml, yaml](scripting/filesystem.md)
|
- [bjson, json, toml, yaml](scripting/filesystem.md)
|
||||||
- [block](scripting/builtins/libblock.md)
|
- [block](scripting/builtins/libblock.md)
|
||||||
- [byteutil](scripting/builtins/libbyteutil.md)
|
- [byteutil](scripting/builtins/libbyteutil.md)
|
||||||
|
- [crypto](scripting/builtins/libcrypto.md)
|
||||||
- [cameras](scripting/builtins/libcameras.md)
|
- [cameras](scripting/builtins/libcameras.md)
|
||||||
- [ctypes](scripting/builtins/libctypes.md)
|
- [ctypes](scripting/builtins/libctypes.md)
|
||||||
- [entities](scripting/builtins/libentities.md)
|
- [entities](scripting/builtins/libentities.md)
|
||||||
|
|
|
||||||
144
doc/en/scripting/builtins/libcrypto.md
Normal file
144
doc/en/scripting/builtins/libcrypto.md
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# *crypto* library
|
||||||
|
|
||||||
|
The library provides common cryptographic functions
|
||||||
|
|
||||||
|
## Hashing
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.sha256(data: str) -> str
|
||||||
|
crypto.sha384(data: str) -> str
|
||||||
|
crypto.sha512(data: str) -> str
|
||||||
|
crypto.md5(data: str) -> str
|
||||||
|
|
||||||
|
crypto.hash(hash: str, data: str) -> str
|
||||||
|
crypto.hmac(hash: str, key: str, data: str) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported names are `SHA256`, `SHA384`, `SHA512` and `MD5`
|
||||||
|
|
||||||
|
MD5 is needed for compatibility with old formats. SHA256 is better for new data
|
||||||
|
|
||||||
|
Large files can be passed in parts
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local hash = crypto.hash_new("SHA256")
|
||||||
|
|
||||||
|
hash:update(part1)
|
||||||
|
hash:update(part2)
|
||||||
|
|
||||||
|
local result = hash:final()
|
||||||
|
```
|
||||||
|
|
||||||
|
The context can be cleared with `reset` after `final`
|
||||||
|
|
||||||
|
## Signatures
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.ed25519_keypair() -> private_key, public_key
|
||||||
|
crypto.ed25519_public(private_key: str) -> str
|
||||||
|
crypto.ed25519_sign(private_key: str, message: str) -> str
|
||||||
|
crypto.ed25519_verify(public_key: str, message: str, signature: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
Ed25519 keys are 32 bytes long. A signature is 64 bytes long
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.ecdsa_keypair(curve: str) -> private_key, public_key
|
||||||
|
crypto.ecdsa_public(curve: str, private_key: str) -> str
|
||||||
|
crypto.ecdsa_sign(curve: str, private_key: str, message: str, hash: str) -> str
|
||||||
|
crypto.ecdsa_verify(curve: str, public_key: str, message: str,
|
||||||
|
signature: str, hash: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
Available curves are `P-256`, `P-384` and `P-521`
|
||||||
|
|
||||||
|
An ECDSA public key is stored as an uncompressed SEC1 point. A signature is
|
||||||
|
stored in DER format
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.rsa_pkcs1_verify(hash: str, modulus: str, exponent: str,
|
||||||
|
message: str, signature: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
|
||||||
|
crypto.rsa_pss_verify(hash: str, modulus: str, exponent: str,
|
||||||
|
message: str, signature: str, salt_length: int)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
RSA modulus and exponent are passed in big endian. A `salt_length` value of `-1`
|
||||||
|
uses the hash size
|
||||||
|
|
||||||
|
## Key exchange
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.x25519_keypair() -> private_key, public_key
|
||||||
|
crypto.x25519_public(private_key: str) -> str
|
||||||
|
crypto.x25519(private_key: str, peer_public_key: str) -> str
|
||||||
|
crypto.x25519_shared(private_key: str, peer_public_key: str) -> str
|
||||||
|
|
||||||
|
crypto.p256_keypair() -> private_key, public_key
|
||||||
|
crypto.p256_public(private_key: str) -> str
|
||||||
|
crypto.p256_shared(private_key: str, peer_public_key: str) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
`x25519` and `x25519_shared` perform the same operation
|
||||||
|
|
||||||
|
The shared secret should not be used as a ready key. Pass it through HKDF first
|
||||||
|
|
||||||
|
## Encryption
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.aes_gcm_encrypt(key: str, nonce: str, aad: str, plaintext: str) -> str
|
||||||
|
crypto.aes_gcm_decrypt(key: str, nonce: str, aad: str, ciphertext: str)
|
||||||
|
-> plaintext
|
||||||
|
-> nil, error
|
||||||
|
|
||||||
|
crypto.chacha20_poly1305_encrypt(key: str, nonce: str, aad: str,
|
||||||
|
plaintext: str) -> str
|
||||||
|
crypto.chacha20_poly1305_decrypt(key: str, nonce: str, aad: str,
|
||||||
|
ciphertext: str)
|
||||||
|
-> plaintext
|
||||||
|
-> nil, error
|
||||||
|
```
|
||||||
|
|
||||||
|
AES GCM accepts 16 and 32 byte keys
|
||||||
|
|
||||||
|
ChaCha20 Poly1305 uses a 32 byte key and a 12 byte nonce
|
||||||
|
|
||||||
|
A 16 byte tag is appended to encrypted data
|
||||||
|
|
||||||
|
Never reuse a nonce with the same key
|
||||||
|
|
||||||
|
## Other functions
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.random_bytes(length: int) -> str
|
||||||
|
crypto.constant_time_equal(left: str, right: str) -> bool
|
||||||
|
|
||||||
|
crypto.hkdf_extract(hash: str, salt: str, ikm: str) -> str
|
||||||
|
crypto.hkdf_expand(hash: str, prk: str, info: str, length: int) -> str
|
||||||
|
|
||||||
|
crypto.pbkdf2(hash: str, password: str, salt: str,
|
||||||
|
iterations: int, length: int) -> str
|
||||||
|
crypto.scrypt(password: str, salt: str, n: int, r: int, p: int,
|
||||||
|
length: int, [optional]max_memory: int=0) -> str
|
||||||
|
|
||||||
|
crypto.features() -> table
|
||||||
|
```
|
||||||
|
|
||||||
|
Use scrypt or PBKDF2 with a random salt for passwords. A regular SHA256 hash is
|
||||||
|
not suitable for passwords
|
||||||
|
|
||||||
|
`features` returns the OpenSSL version and a list of available features
|
||||||
|
|
||||||
|
An invalid signature returns `false, "invalid_signature"`
|
||||||
|
|
||||||
|
An invalid encryption tag returns `nil, "authentication_failed"`
|
||||||
|
|
||||||
|
Invalid arguments raise a regular Lua error
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
- [bjson, json, toml, yaml](scripting/filesystem.md)
|
- [bjson, json, toml, yaml](scripting/filesystem.md)
|
||||||
- [block](scripting/builtins/libblock.md)
|
- [block](scripting/builtins/libblock.md)
|
||||||
- [byteutil](scripting/builtins/libbyteutil.md)
|
- [byteutil](scripting/builtins/libbyteutil.md)
|
||||||
|
- [crypto](scripting/builtins/libcrypto.md)
|
||||||
- [cameras](scripting/builtins/libcameras.md)
|
- [cameras](scripting/builtins/libcameras.md)
|
||||||
- [ctypes](scripting/builtins/libctypes.md)
|
- [ctypes](scripting/builtins/libctypes.md)
|
||||||
- [entities](scripting/builtins/libentities.md)
|
- [entities](scripting/builtins/libentities.md)
|
||||||
|
|
|
||||||
146
doc/ru/scripting/builtins/libcrypto.md
Normal file
146
doc/ru/scripting/builtins/libcrypto.md
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
# Библиотека *crypto*
|
||||||
|
|
||||||
|
Библиотека предоставляет основные криптографические функции
|
||||||
|
|
||||||
|
## Хеширование
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.sha256(data: str) -> str
|
||||||
|
crypto.sha384(data: str) -> str
|
||||||
|
crypto.sha512(data: str) -> str
|
||||||
|
crypto.md5(data: str) -> str
|
||||||
|
|
||||||
|
crypto.hash(hash: str, data: str) -> str
|
||||||
|
crypto.hmac(hash: str, key: str, data: str) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
Поддерживаются `SHA256`, `SHA384`, `SHA512` и `MD5`
|
||||||
|
|
||||||
|
MD5 нужен для совместимости со старыми форматами. Для новых данных лучше
|
||||||
|
использовать SHA256
|
||||||
|
|
||||||
|
Большой файл можно передавать частями
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local hash = crypto.hash_new("SHA256")
|
||||||
|
|
||||||
|
hash:update(part1)
|
||||||
|
hash:update(part2)
|
||||||
|
|
||||||
|
local result = hash:final()
|
||||||
|
```
|
||||||
|
|
||||||
|
После `final` контекст можно очистить через `reset`
|
||||||
|
|
||||||
|
## Подписи
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.ed25519_keypair() -> private_key, public_key
|
||||||
|
crypto.ed25519_public(private_key: str) -> str
|
||||||
|
crypto.ed25519_sign(private_key: str, message: str) -> str
|
||||||
|
crypto.ed25519_verify(public_key: str, message: str, signature: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
Ключи Ed25519 имеют длину 32 байта. Подпись имеет длину 64 байта
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.ecdsa_keypair(curve: str) -> private_key, public_key
|
||||||
|
crypto.ecdsa_public(curve: str, private_key: str) -> str
|
||||||
|
crypto.ecdsa_sign(curve: str, private_key: str, message: str, hash: str) -> str
|
||||||
|
crypto.ecdsa_verify(curve: str, public_key: str, message: str,
|
||||||
|
signature: str, hash: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
Доступны кривые `P-256`, `P-384` и `P-521`
|
||||||
|
|
||||||
|
Публичный ключ ECDSA записывается как несжатая SEC1 точка. Подпись хранится в
|
||||||
|
DER формате
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.rsa_pkcs1_verify(hash: str, modulus: str, exponent: str,
|
||||||
|
message: str, signature: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
|
||||||
|
crypto.rsa_pss_verify(hash: str, modulus: str, exponent: str,
|
||||||
|
message: str, signature: str, salt_length: int)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
Модуль и экспонента RSA передаются в big endian. Значение `salt_length = -1`
|
||||||
|
использует размер хеша
|
||||||
|
|
||||||
|
## Обмен ключами
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.x25519_keypair() -> private_key, public_key
|
||||||
|
crypto.x25519_public(private_key: str) -> str
|
||||||
|
crypto.x25519(private_key: str, peer_public_key: str) -> str
|
||||||
|
crypto.x25519_shared(private_key: str, peer_public_key: str) -> str
|
||||||
|
|
||||||
|
crypto.p256_keypair() -> private_key, public_key
|
||||||
|
crypto.p256_public(private_key: str) -> str
|
||||||
|
crypto.p256_shared(private_key: str, peer_public_key: str) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
`x25519` и `x25519_shared` выполняют одну и ту же операцию
|
||||||
|
|
||||||
|
Общий секрет не стоит использовать как готовый ключ. Сначала его нужно
|
||||||
|
обработать через HKDF
|
||||||
|
|
||||||
|
## Шифрование
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.aes_gcm_encrypt(key: str, nonce: str, aad: str, plaintext: str) -> str
|
||||||
|
crypto.aes_gcm_decrypt(key: str, nonce: str, aad: str, ciphertext: str)
|
||||||
|
-> plaintext
|
||||||
|
-> nil, error
|
||||||
|
|
||||||
|
crypto.chacha20_poly1305_encrypt(key: str, nonce: str, aad: str,
|
||||||
|
plaintext: str) -> str
|
||||||
|
crypto.chacha20_poly1305_decrypt(key: str, nonce: str, aad: str,
|
||||||
|
ciphertext: str)
|
||||||
|
-> plaintext
|
||||||
|
-> nil, error
|
||||||
|
```
|
||||||
|
|
||||||
|
AES GCM принимает ключи размером 16 или 32 байта
|
||||||
|
|
||||||
|
ChaCha20 Poly1305 использует ключ размером 32 байта и nonce размером 12 байт
|
||||||
|
|
||||||
|
В конец зашифрованных данных добавляется тег размером 16 байт
|
||||||
|
|
||||||
|
Нельзя повторно использовать один nonce с тем же ключом
|
||||||
|
|
||||||
|
## Остальные функции
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.random_bytes(length: int) -> str
|
||||||
|
crypto.constant_time_equal(left: str, right: str) -> bool
|
||||||
|
|
||||||
|
crypto.hkdf_extract(hash: str, salt: str, ikm: str) -> str
|
||||||
|
crypto.hkdf_expand(hash: str, prk: str, info: str, length: int) -> str
|
||||||
|
|
||||||
|
crypto.pbkdf2(hash: str, password: str, salt: str,
|
||||||
|
iterations: int, length: int) -> str
|
||||||
|
crypto.scrypt(password: str, salt: str, n: int, r: int, p: int,
|
||||||
|
length: int, [опционально]max_memory: int=0) -> str
|
||||||
|
|
||||||
|
crypto.features() -> table
|
||||||
|
```
|
||||||
|
|
||||||
|
Для хранения паролей следует использовать scrypt или PBKDF2 со случайной
|
||||||
|
солью. Обычный SHA256 для паролей не подходит
|
||||||
|
|
||||||
|
`features` возвращает версию OpenSSL и список доступных возможностей
|
||||||
|
|
||||||
|
Неверная подпись возвращает `false, "invalid_signature"`
|
||||||
|
|
||||||
|
Неверный тег при расшифровке возвращает `nil, "authentication_failed"`
|
||||||
|
|
||||||
|
Ошибки в аргументах вызывают обычную Lua ошибку
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
openal
|
openal
|
||||||
luajit
|
luajit
|
||||||
curl
|
curl
|
||||||
|
openssl
|
||||||
entt
|
entt
|
||||||
mesa
|
mesa
|
||||||
freeglut
|
freeglut
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ endif()
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
find_package(CURL REQUIRED)
|
find_package(CURL REQUIRED)
|
||||||
|
find_package(OpenSSL 3.0 REQUIRED COMPONENTS Crypto)
|
||||||
find_package(glfw3 REQUIRED)
|
find_package(glfw3 REQUIRED)
|
||||||
find_package(Freetype REQUIRED)
|
find_package(Freetype REQUIRED)
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
|
|
@ -79,12 +80,13 @@ target_link_libraries(
|
||||||
ZLIB::ZLIB
|
ZLIB::ZLIB
|
||||||
PNG::PNG
|
PNG::PNG
|
||||||
CURL::libcurl
|
CURL::libcurl
|
||||||
|
OpenSSL::Crypto
|
||||||
OpenAL::OpenAL
|
OpenAL::OpenAL
|
||||||
Vorbis::vorbis
|
Vorbis::vorbis
|
||||||
Vorbis::vorbisfile
|
Vorbis::vorbisfile
|
||||||
luajit::luajit
|
|
||||||
Freetype::Freetype
|
Freetype::Freetype
|
||||||
PUBLIC glm::glm # Need public for src/delegates.hpp, which including to
|
PUBLIC luajit::luajit
|
||||||
|
glm::glm # Need public for src/delegates.hpp, which including to
|
||||||
# main.cpp
|
# main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
1247
src/crypto/Crypto.cpp
Normal file
1247
src/crypto/Crypto.cpp
Normal file
File diff suppressed because it is too large
Load diff
185
src/crypto/Crypto.hpp
Normal file
185
src/crypto/Crypto.hpp
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace crypto {
|
||||||
|
constexpr int API_VERSION = 1;
|
||||||
|
using Bytes = std::vector<std::uint8_t>;
|
||||||
|
|
||||||
|
enum class ErrorCode {
|
||||||
|
InvalidArgument,
|
||||||
|
InvalidKey,
|
||||||
|
InvalidSignature,
|
||||||
|
AuthenticationFailed,
|
||||||
|
UnsupportedAlgorithm,
|
||||||
|
OutputTooLarge,
|
||||||
|
InvalidState,
|
||||||
|
BackendError
|
||||||
|
};
|
||||||
|
|
||||||
|
class Error : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
Error(ErrorCode code, const std::string& message);
|
||||||
|
ErrorCode code() const noexcept;
|
||||||
|
const char* codeName() const noexcept;
|
||||||
|
private:
|
||||||
|
ErrorCode errorCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyPair {
|
||||||
|
Bytes privateKey;
|
||||||
|
Bytes publicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Features {
|
||||||
|
bool sha256;
|
||||||
|
bool sha384;
|
||||||
|
bool sha512;
|
||||||
|
bool md5;
|
||||||
|
bool hmac;
|
||||||
|
bool ed25519;
|
||||||
|
bool ecdsa;
|
||||||
|
bool rsa;
|
||||||
|
bool x25519;
|
||||||
|
bool p256;
|
||||||
|
bool aes128Gcm;
|
||||||
|
bool aes256Gcm;
|
||||||
|
bool chacha20Poly1305;
|
||||||
|
bool random;
|
||||||
|
bool hkdf;
|
||||||
|
bool pbkdf2;
|
||||||
|
bool scrypt;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HashContext {
|
||||||
|
public:
|
||||||
|
explicit HashContext(std::string_view hash);
|
||||||
|
~HashContext();
|
||||||
|
HashContext(HashContext&&) noexcept;
|
||||||
|
HashContext& operator=(HashContext&&) noexcept;
|
||||||
|
HashContext(const HashContext&) = delete;
|
||||||
|
HashContext& operator=(const HashContext&) = delete;
|
||||||
|
|
||||||
|
void update(std::string_view data);
|
||||||
|
Bytes final();
|
||||||
|
void reset();
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
std::unique_ptr<Impl> impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
Bytes digest(std::string_view hash, std::string_view data);
|
||||||
|
Bytes hmac(
|
||||||
|
std::string_view hash, std::string_view key, std::string_view data
|
||||||
|
);
|
||||||
|
|
||||||
|
KeyPair ed25519KeyPair();
|
||||||
|
Bytes ed25519Public(std::string_view privateKey);
|
||||||
|
Bytes ed25519Sign(std::string_view privateKey, std::string_view message);
|
||||||
|
|
||||||
|
bool ed25519Verify(
|
||||||
|
std::string_view publicKey,
|
||||||
|
std::string_view message,
|
||||||
|
std::string_view signature
|
||||||
|
);
|
||||||
|
bool ecdsaVerify(
|
||||||
|
std::string_view curve,
|
||||||
|
std::string_view publicKey,
|
||||||
|
std::string_view message,
|
||||||
|
std::string_view signature,
|
||||||
|
std::string_view hash
|
||||||
|
);
|
||||||
|
KeyPair ecdsaKeyPair(std::string_view curve);
|
||||||
|
Bytes ecdsaPublic(std::string_view curve, std::string_view privateKey);
|
||||||
|
Bytes ecdsaSign(
|
||||||
|
std::string_view curve,
|
||||||
|
std::string_view privateKey,
|
||||||
|
std::string_view message,
|
||||||
|
std::string_view hash
|
||||||
|
);
|
||||||
|
bool rsaPkcs1Verify(
|
||||||
|
std::string_view hash,
|
||||||
|
std::string_view modulus,
|
||||||
|
std::string_view exponent,
|
||||||
|
std::string_view message,
|
||||||
|
std::string_view signature
|
||||||
|
);
|
||||||
|
bool rsaPssVerify(
|
||||||
|
std::string_view hash,
|
||||||
|
std::string_view modulus,
|
||||||
|
std::string_view exponent,
|
||||||
|
std::string_view message,
|
||||||
|
std::string_view signature,
|
||||||
|
int saltLength
|
||||||
|
);
|
||||||
|
|
||||||
|
KeyPair x25519KeyPair();
|
||||||
|
Bytes x25519Public(std::string_view privateKey);
|
||||||
|
Bytes x25519(std::string_view privateKey, std::string_view peerPublicKey);
|
||||||
|
Bytes p256Public(std::string_view privateKey);
|
||||||
|
Bytes p256Shared(
|
||||||
|
std::string_view privateKey, std::string_view peerPublicKey
|
||||||
|
);
|
||||||
|
|
||||||
|
Bytes aesGcmEncrypt(
|
||||||
|
std::string_view key,
|
||||||
|
std::string_view nonce,
|
||||||
|
std::string_view aad,
|
||||||
|
std::string_view plaintext
|
||||||
|
);
|
||||||
|
Bytes aesGcmDecrypt(
|
||||||
|
std::string_view key,
|
||||||
|
std::string_view nonce,
|
||||||
|
std::string_view aad,
|
||||||
|
std::string_view ciphertextWithTag
|
||||||
|
);
|
||||||
|
Bytes chacha20Poly1305Encrypt(
|
||||||
|
std::string_view key,
|
||||||
|
std::string_view nonce,
|
||||||
|
std::string_view aad,
|
||||||
|
std::string_view plaintext
|
||||||
|
);
|
||||||
|
Bytes chacha20Poly1305Decrypt(
|
||||||
|
std::string_view key,
|
||||||
|
std::string_view nonce,
|
||||||
|
std::string_view aad,
|
||||||
|
std::string_view ciphertextWithTag
|
||||||
|
);
|
||||||
|
|
||||||
|
Bytes randomBytes(std::size_t length);
|
||||||
|
bool constantTimeEqual(std::string_view left, std::string_view right);
|
||||||
|
Bytes hkdfExtract(
|
||||||
|
std::string_view hash, std::string_view salt, std::string_view ikm
|
||||||
|
);
|
||||||
|
Bytes hkdfExpand(
|
||||||
|
std::string_view hash,
|
||||||
|
std::string_view prk,
|
||||||
|
std::string_view info,
|
||||||
|
std::size_t length
|
||||||
|
);
|
||||||
|
Bytes pbkdf2(
|
||||||
|
std::string_view hash,
|
||||||
|
std::string_view password,
|
||||||
|
std::string_view salt,
|
||||||
|
std::uint32_t iterations,
|
||||||
|
std::size_t length
|
||||||
|
);
|
||||||
|
Bytes scrypt(
|
||||||
|
std::string_view password,
|
||||||
|
std::string_view salt,
|
||||||
|
std::uint64_t n,
|
||||||
|
std::uint64_t r,
|
||||||
|
std::uint64_t p,
|
||||||
|
std::size_t length,
|
||||||
|
std::uint64_t maxMemory = 0
|
||||||
|
);
|
||||||
|
|
||||||
|
const char* backendVersion();
|
||||||
|
Features features();
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ extern const luaL_Reg byteutillib[];
|
||||||
extern const luaL_Reg cameralib[];
|
extern const luaL_Reg cameralib[];
|
||||||
extern const luaL_Reg consolelib[];
|
extern const luaL_Reg consolelib[];
|
||||||
extern const luaL_Reg corelib[];
|
extern const luaL_Reg corelib[];
|
||||||
|
extern const luaL_Reg cryptolib[];
|
||||||
extern const luaL_Reg entitylib[];
|
extern const luaL_Reg entitylib[];
|
||||||
extern const luaL_Reg filelib[];
|
extern const luaL_Reg filelib[];
|
||||||
extern const luaL_Reg generationlib[];
|
extern const luaL_Reg generationlib[];
|
||||||
|
|
@ -93,3 +94,4 @@ namespace lua {
|
||||||
}
|
}
|
||||||
|
|
||||||
void initialize_libs_extends(lua::State* L);
|
void initialize_libs_extends(lua::State* L);
|
||||||
|
void initialize_cryptolib(lua::State* L);
|
||||||
|
|
|
||||||
540
src/logic/scripting/lua/libs/libcrypto.cpp
Normal file
540
src/logic/scripting/lua/libs/libcrypto.cpp
Normal file
|
|
@ -0,0 +1,540 @@
|
||||||
|
#include <climits>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <new>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "api_lua.hpp"
|
||||||
|
#include "crypto/Crypto.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using HashContext = crypto::HashContext;
|
||||||
|
|
||||||
|
constexpr const char* HASH_CONTEXT_METATABLE =
|
||||||
|
"voxelcore.crypto.HashContext";
|
||||||
|
|
||||||
|
int pushBytes(lua::State* L, const crypto::Bytes& value) {
|
||||||
|
return lua::pushlstring(L, value.data(), value.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int pushFailure(lua::State* L, bool nil, const char* error) {
|
||||||
|
nil ? lua::pushnil(L) : lua::pushboolean(L, false);
|
||||||
|
lua::pushstring(L, error);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pushKeyPair(lua::State* L, const crypto::KeyPair& keyPair) {
|
||||||
|
pushBytes(L, keyPair.privateKey);
|
||||||
|
pushBytes(L, keyPair.publicKey);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Operation>
|
||||||
|
int verify(lua::State* L, Operation operation) {
|
||||||
|
try {
|
||||||
|
if (operation()) return lua::pushboolean(L, true);
|
||||||
|
return pushFailure(L, false, "invalid_signature");
|
||||||
|
} catch (const crypto::Error& error) {
|
||||||
|
return pushFailure(L, false, error.codeName());
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
return pushFailure(L, false, "backend_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Operation>
|
||||||
|
int decrypt(lua::State* L, Operation operation) {
|
||||||
|
try {
|
||||||
|
return pushBytes(L, operation());
|
||||||
|
} catch (const crypto::Error& error) {
|
||||||
|
return pushFailure(L, true, error.codeName());
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
return pushFailure(L, true, "backend_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t requireSize(lua::State* L, int index, const char* name) {
|
||||||
|
if (lua::type(L, index) != LUA_TNUMBER) {
|
||||||
|
throw std::runtime_error(std::string(name) + " must be a number");
|
||||||
|
}
|
||||||
|
const lua::Number number = lua::tonumber(L, index);
|
||||||
|
const lua::Integer value = lua::tointeger(L, index);
|
||||||
|
if (number != static_cast<lua::Number>(value)) {
|
||||||
|
throw std::runtime_error(std::string(name) + " must be an integer");
|
||||||
|
}
|
||||||
|
if (value < 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
std::string(name) + " must be non-negative"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return static_cast<std::size_t>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int requireInt(lua::State* L, int index, const char* name) {
|
||||||
|
if (lua::type(L, index) != LUA_TNUMBER) {
|
||||||
|
throw std::runtime_error(std::string(name) + " must be a number");
|
||||||
|
}
|
||||||
|
const lua::Number number = lua::tonumber(L, index);
|
||||||
|
const lua::Integer value = lua::tointeger(L, index);
|
||||||
|
if (number != static_cast<lua::Number>(value)) {
|
||||||
|
throw std::runtime_error(std::string(name) + " must be an integer");
|
||||||
|
}
|
||||||
|
if (value < INT_MIN || value > INT_MAX) {
|
||||||
|
throw std::runtime_error(std::string(name) + " is out of range");
|
||||||
|
}
|
||||||
|
return static_cast<int>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int hash(lua::State* L, std::string_view name) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, crypto::digest(name, lua::require_lstring(L, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_sha256(lua::State* L) {
|
||||||
|
return hash(L, "SHA256");
|
||||||
|
}
|
||||||
|
int l_sha384(lua::State* L) {
|
||||||
|
return hash(L, "SHA384");
|
||||||
|
}
|
||||||
|
int l_sha512(lua::State* L) {
|
||||||
|
return hash(L, "SHA512");
|
||||||
|
}
|
||||||
|
int l_md5(lua::State* L) {
|
||||||
|
return hash(L, "MD5");
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::digest(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto::HashContext* requireHashContext(lua::State* L) {
|
||||||
|
return static_cast<crypto::HashContext*>(
|
||||||
|
luaL_checkudata(L, 1, HASH_CONTEXT_METATABLE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash_context_gc(lua::State* L) {
|
||||||
|
requireHashContext(L)->~HashContext();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash_context_update(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
requireHashContext(L)->update(lua::require_lstring(L, 2));
|
||||||
|
lua::pushvalue(L, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash_context_final(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, requireHashContext(L)->final());
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash_context_reset(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
requireHashContext(L)->reset();
|
||||||
|
lua::pushvalue(L, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ensureHashContextMetatable(lua::State* L) {
|
||||||
|
if (luaL_newmetatable(L, HASH_CONTEXT_METATABLE)) {
|
||||||
|
lua::pushcfunction(L, lua::wrap<l_hash_context_gc>);
|
||||||
|
lua::setfield(L, "__gc");
|
||||||
|
lua::pushcfunction(L, lua::wrap<l_hash_context_update>);
|
||||||
|
lua::setfield(L, "update");
|
||||||
|
lua::pushcfunction(L, lua::wrap<l_hash_context_final>);
|
||||||
|
lua::setfield(L, "final");
|
||||||
|
lua::pushcfunction(L, lua::wrap<l_hash_context_reset>);
|
||||||
|
lua::setfield(L, "reset");
|
||||||
|
lua::pushvalue(L, -1);
|
||||||
|
lua::setfield(L, "__index");
|
||||||
|
}
|
||||||
|
lua::pop(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash_new(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
const auto name = lua::require_lstring(L, 1);
|
||||||
|
ensureHashContextMetatable(L);
|
||||||
|
void* memory = lua_newuserdata(L, sizeof(crypto::HashContext));
|
||||||
|
new (memory) crypto::HashContext(name);
|
||||||
|
luaL_getmetatable(L, HASH_CONTEXT_METATABLE);
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hmac(lua::State* L) {
|
||||||
|
lua::check_argc(L, 3);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::hmac(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ed25519_verify(lua::State* L) {
|
||||||
|
lua::check_argc(L, 3);
|
||||||
|
const auto key = lua::require_lstring(L, 1);
|
||||||
|
const auto message = lua::require_lstring(L, 2);
|
||||||
|
const auto signature = lua::require_lstring(L, 3);
|
||||||
|
return verify(L, [=] {
|
||||||
|
return crypto::ed25519Verify(key, message, signature);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ed25519_keypair(lua::State* L) {
|
||||||
|
lua::check_argc(L, 0);
|
||||||
|
return pushKeyPair(L, crypto::ed25519KeyPair());
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ed25519_public(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, crypto::ed25519Public(lua::require_lstring(L, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ed25519_sign(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::ed25519Sign(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ecdsa_verify(lua::State* L) {
|
||||||
|
lua::check_argc(L, 5);
|
||||||
|
const auto curve = lua::require_lstring(L, 1);
|
||||||
|
const auto key = lua::require_lstring(L, 2);
|
||||||
|
const auto message = lua::require_lstring(L, 3);
|
||||||
|
const auto signature = lua::require_lstring(L, 4);
|
||||||
|
const auto hashName = lua::require_lstring(L, 5);
|
||||||
|
return verify(L, [=] {
|
||||||
|
return crypto::ecdsaVerify(
|
||||||
|
curve, key, message, signature, hashName
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ecdsa_keypair(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushKeyPair(L, crypto::ecdsaKeyPair(lua::require_lstring(L, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ecdsa_public(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::ecdsaPublic(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ecdsa_sign(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::ecdsaSign(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3),
|
||||||
|
lua::require_lstring(L, 4)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_rsa_pkcs1_verify(lua::State* L) {
|
||||||
|
lua::check_argc(L, 5);
|
||||||
|
const auto hashName = lua::require_lstring(L, 1);
|
||||||
|
const auto modulus = lua::require_lstring(L, 2);
|
||||||
|
const auto exponent = lua::require_lstring(L, 3);
|
||||||
|
const auto message = lua::require_lstring(L, 4);
|
||||||
|
const auto signature = lua::require_lstring(L, 5);
|
||||||
|
return verify(L, [=] {
|
||||||
|
return crypto::rsaPkcs1Verify(
|
||||||
|
hashName, modulus, exponent, message, signature
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_rsa_pss_verify(lua::State* L) {
|
||||||
|
lua::check_argc(L, 6);
|
||||||
|
const auto hashName = lua::require_lstring(L, 1);
|
||||||
|
const auto modulus = lua::require_lstring(L, 2);
|
||||||
|
const auto exponent = lua::require_lstring(L, 3);
|
||||||
|
const auto message = lua::require_lstring(L, 4);
|
||||||
|
const auto signature = lua::require_lstring(L, 5);
|
||||||
|
const int saltLength = requireInt(L, 6, "salt_length");
|
||||||
|
return verify(L, [=] {
|
||||||
|
return crypto::rsaPssVerify(
|
||||||
|
hashName, modulus, exponent, message, signature, saltLength
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_x25519_public(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, crypto::x25519Public(lua::require_lstring(L, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_x25519_keypair(lua::State* L) {
|
||||||
|
lua::check_argc(L, 0);
|
||||||
|
return pushKeyPair(L, crypto::x25519KeyPair());
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_x25519(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::x25519(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_p256_public(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, crypto::p256Public(lua::require_lstring(L, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_p256_keypair(lua::State* L) {
|
||||||
|
lua::check_argc(L, 0);
|
||||||
|
return pushKeyPair(L, crypto::ecdsaKeyPair("P-256"));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_p256_shared(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::p256Shared(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_aes_gcm_encrypt(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::aesGcmEncrypt(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3),
|
||||||
|
lua::require_lstring(L, 4)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_aes_gcm_decrypt(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
const auto key = lua::require_lstring(L, 1);
|
||||||
|
const auto nonce = lua::require_lstring(L, 2);
|
||||||
|
const auto aad = lua::require_lstring(L, 3);
|
||||||
|
const auto ciphertext = lua::require_lstring(L, 4);
|
||||||
|
return decrypt(L, [=] {
|
||||||
|
return crypto::aesGcmDecrypt(key, nonce, aad, ciphertext);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_chacha20_poly1305_encrypt(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::chacha20Poly1305Encrypt(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3),
|
||||||
|
lua::require_lstring(L, 4)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_chacha20_poly1305_decrypt(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
const auto key = lua::require_lstring(L, 1);
|
||||||
|
const auto nonce = lua::require_lstring(L, 2);
|
||||||
|
const auto aad = lua::require_lstring(L, 3);
|
||||||
|
const auto ciphertext = lua::require_lstring(L, 4);
|
||||||
|
return decrypt(L, [=] {
|
||||||
|
return crypto::chacha20Poly1305Decrypt(key, nonce, aad, ciphertext);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_random_bytes(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, crypto::randomBytes(requireSize(L, 1, "length")));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_constant_time_equal(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return lua::pushboolean(
|
||||||
|
L,
|
||||||
|
crypto::constantTimeEqual(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hkdf_extract(lua::State* L) {
|
||||||
|
lua::check_argc(L, 3);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::hkdfExtract(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hkdf_expand(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::hkdfExpand(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3),
|
||||||
|
requireSize(L, 4, "length")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_pbkdf2(lua::State* L) {
|
||||||
|
lua::check_argc(L, 5);
|
||||||
|
const auto iterations = requireSize(L, 4, "iterations");
|
||||||
|
if (iterations == 0 || iterations > UINT32_MAX) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"iterations must be between 1 and 4294967295"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::pbkdf2(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3),
|
||||||
|
static_cast<std::uint32_t>(iterations),
|
||||||
|
requireSize(L, 5, "length")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_scrypt(lua::State* L) {
|
||||||
|
const auto argc = lua::check_argc(L, 6, 7);
|
||||||
|
const auto maxMemory =
|
||||||
|
argc == 7 ? requireSize(L, 7, "max_memory") : std::size_t {0};
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::scrypt(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
requireSize(L, 3, "n"),
|
||||||
|
requireSize(L, 4, "r"),
|
||||||
|
requireSize(L, 5, "p"),
|
||||||
|
requireSize(L, 6, "length"),
|
||||||
|
maxMemory
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFeature(lua::State* L, const char* name, bool available) {
|
||||||
|
lua::pushboolean(L, available);
|
||||||
|
lua::setfield(L, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_features(lua::State* L) {
|
||||||
|
lua::check_argc(L, 0);
|
||||||
|
const auto features = crypto::features();
|
||||||
|
lua::createtable(L, 0, 26);
|
||||||
|
lua::pushstring(L, "OpenSSL");
|
||||||
|
lua::setfield(L, "backend");
|
||||||
|
lua::pushstring(L, crypto::backendVersion());
|
||||||
|
lua::setfield(L, "backend_version");
|
||||||
|
lua::pushinteger(L, crypto::API_VERSION);
|
||||||
|
lua::setfield(L, "api_version");
|
||||||
|
setFeature(L, "sha256", features.sha256);
|
||||||
|
setFeature(L, "sha384", features.sha384);
|
||||||
|
setFeature(L, "sha512", features.sha512);
|
||||||
|
setFeature(L, "md5", features.md5);
|
||||||
|
setFeature(L, "hmac", features.hmac);
|
||||||
|
setFeature(L, "streaming_hash", features.sha256);
|
||||||
|
setFeature(L, "ed25519", features.ed25519);
|
||||||
|
setFeature(L, "ed25519_verify", features.ed25519);
|
||||||
|
setFeature(L, "ecdsa", features.ecdsa);
|
||||||
|
setFeature(L, "ecdsa_verify", features.ecdsa);
|
||||||
|
setFeature(L, "rsa_pkcs1_verify", features.rsa);
|
||||||
|
setFeature(L, "rsa_pss_verify", features.rsa);
|
||||||
|
setFeature(L, "x25519", features.x25519);
|
||||||
|
setFeature(L, "p256", features.p256);
|
||||||
|
setFeature(L, "aes_128_gcm", features.aes128Gcm);
|
||||||
|
setFeature(L, "aes_256_gcm", features.aes256Gcm);
|
||||||
|
setFeature(L, "aes_gcm", features.aes128Gcm && features.aes256Gcm);
|
||||||
|
setFeature(L, "chacha20_poly1305", features.chacha20Poly1305);
|
||||||
|
setFeature(L, "random_bytes", features.random);
|
||||||
|
setFeature(L, "constant_time_equal", true);
|
||||||
|
setFeature(L, "hkdf", features.hkdf);
|
||||||
|
setFeature(L, "pbkdf2", features.pbkdf2);
|
||||||
|
setFeature(L, "scrypt", features.scrypt);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const luaL_Reg cryptolib[] = {
|
||||||
|
{"sha256", lua::wrap<l_sha256>},
|
||||||
|
{"sha384", lua::wrap<l_sha384>},
|
||||||
|
{"sha512", lua::wrap<l_sha512>},
|
||||||
|
{"md5", lua::wrap<l_md5>},
|
||||||
|
{"hash", lua::wrap<l_hash>},
|
||||||
|
{"hash_new", lua::wrap<l_hash_new>},
|
||||||
|
{"hmac", lua::wrap<l_hmac>},
|
||||||
|
{"ed25519_keypair", lua::wrap<l_ed25519_keypair>},
|
||||||
|
{"ed25519_public", lua::wrap<l_ed25519_public>},
|
||||||
|
{"ed25519_sign", lua::wrap<l_ed25519_sign>},
|
||||||
|
{"ed25519_verify", lua::wrap<l_ed25519_verify>},
|
||||||
|
{"ecdsa_keypair", lua::wrap<l_ecdsa_keypair>},
|
||||||
|
{"ecdsa_public", lua::wrap<l_ecdsa_public>},
|
||||||
|
{"ecdsa_sign", lua::wrap<l_ecdsa_sign>},
|
||||||
|
{"ecdsa_verify", lua::wrap<l_ecdsa_verify>},
|
||||||
|
{"rsa_pkcs1_verify", lua::wrap<l_rsa_pkcs1_verify>},
|
||||||
|
{"rsa_pss_verify", lua::wrap<l_rsa_pss_verify>},
|
||||||
|
{"x25519_keypair", lua::wrap<l_x25519_keypair>},
|
||||||
|
{"x25519_public", lua::wrap<l_x25519_public>},
|
||||||
|
{"x25519", lua::wrap<l_x25519>},
|
||||||
|
{"x25519_shared", lua::wrap<l_x25519>},
|
||||||
|
{"p256_keypair", lua::wrap<l_p256_keypair>},
|
||||||
|
{"p256_public", lua::wrap<l_p256_public>},
|
||||||
|
{"p256_shared", lua::wrap<l_p256_shared>},
|
||||||
|
{"aes_gcm_encrypt", lua::wrap<l_aes_gcm_encrypt>},
|
||||||
|
{"aes_gcm_decrypt", lua::wrap<l_aes_gcm_decrypt>},
|
||||||
|
{"chacha20_poly1305_encrypt", lua::wrap<l_chacha20_poly1305_encrypt>},
|
||||||
|
{"chacha20_poly1305_decrypt", lua::wrap<l_chacha20_poly1305_decrypt>},
|
||||||
|
{"random_bytes", lua::wrap<l_random_bytes>},
|
||||||
|
{"constant_time_equal", lua::wrap<l_constant_time_equal>},
|
||||||
|
{"hkdf_extract", lua::wrap<l_hkdf_extract>},
|
||||||
|
{"hkdf_expand", lua::wrap<l_hkdf_expand>},
|
||||||
|
{"pbkdf2", lua::wrap<l_pbkdf2>},
|
||||||
|
{"scrypt", lua::wrap<l_scrypt>},
|
||||||
|
{"features", lua::wrap<l_features>},
|
||||||
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
void initialize_cryptolib(lua::State* L) {
|
||||||
|
ensureHashContextMetatable(L);
|
||||||
|
if (lua::getglobal(L, "crypto")) {
|
||||||
|
lua::pushinteger(L, crypto::API_VERSION);
|
||||||
|
lua::setfield(L, "API_VERSION");
|
||||||
|
lua::pop(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,8 @@ static void create_libs(State* L, StateType stateType) {
|
||||||
openlib(L, "bjson", bjsonlib);
|
openlib(L, "bjson", bjsonlib);
|
||||||
openlib(L, "block", blocklib);
|
openlib(L, "block", blocklib);
|
||||||
openlib(L, "byteutil", byteutillib);
|
openlib(L, "byteutil", byteutillib);
|
||||||
|
openlib(L, "crypto", cryptolib);
|
||||||
|
initialize_cryptolib(L);
|
||||||
openlib(L, "file", filelib);
|
openlib(L, "file", filelib);
|
||||||
openlib(L, "generation", generationlib);
|
openlib(L, "generation", generationlib);
|
||||||
openlib(L, "item", itemlib);
|
openlib(L, "item", itemlib);
|
||||||
|
|
|
||||||
479
test/crypto/Crypto.cpp
Normal file
479
test/crypto/Crypto.cpp
Normal file
|
|
@ -0,0 +1,479 @@
|
||||||
|
#include "crypto/Crypto.hpp"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <openssl/bn.h>
|
||||||
|
#include <openssl/core_names.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using PkeyHandle = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>;
|
||||||
|
using MdCtxHandle = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>;
|
||||||
|
|
||||||
|
crypto::Bytes hex(const std::string& value) {
|
||||||
|
crypto::Bytes result;
|
||||||
|
result.reserve(value.size() / 2);
|
||||||
|
for (std::size_t i = 0; i < value.size(); i += 2) {
|
||||||
|
result.push_back(
|
||||||
|
static_cast<std::uint8_t>(
|
||||||
|
std::stoul(value.substr(i, 2), nullptr, 16)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string binary(const crypto::Bytes& value) {
|
||||||
|
return {reinterpret_cast<const char*>(value.data()), value.size()};
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto::Bytes sign(
|
||||||
|
EVP_PKEY* key,
|
||||||
|
const EVP_MD* digest,
|
||||||
|
std::string_view message,
|
||||||
|
int rsaPadding = 0,
|
||||||
|
int saltLength = 0
|
||||||
|
) {
|
||||||
|
MdCtxHandle ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
||||||
|
EVP_PKEY_CTX* pkeyCtx = nullptr;
|
||||||
|
EXPECT_NE(ctx, nullptr);
|
||||||
|
EXPECT_EQ(
|
||||||
|
EVP_DigestSignInit(ctx.get(), &pkeyCtx, digest, nullptr, key), 1
|
||||||
|
);
|
||||||
|
if (rsaPadding != 0) {
|
||||||
|
EXPECT_GT(EVP_PKEY_CTX_set_rsa_padding(pkeyCtx, rsaPadding), 0);
|
||||||
|
if (rsaPadding == RSA_PKCS1_PSS_PADDING) {
|
||||||
|
EXPECT_GT(
|
||||||
|
EVP_PKEY_CTX_set_rsa_pss_saltlen(pkeyCtx, saltLength), 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_EQ(
|
||||||
|
EVP_DigestSignUpdate(ctx.get(), message.data(), message.size()), 1
|
||||||
|
);
|
||||||
|
std::size_t size = 0;
|
||||||
|
EXPECT_EQ(EVP_DigestSignFinal(ctx.get(), nullptr, &size), 1);
|
||||||
|
crypto::Bytes signature(size);
|
||||||
|
EXPECT_EQ(EVP_DigestSignFinal(ctx.get(), signature.data(), &size), 1);
|
||||||
|
signature.resize(size);
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto::Bytes keyInteger(EVP_PKEY* key, const char* name) {
|
||||||
|
BIGNUM* raw = nullptr;
|
||||||
|
EXPECT_EQ(EVP_PKEY_get_bn_param(key, name, &raw), 1);
|
||||||
|
std::unique_ptr<BIGNUM, decltype(&BN_free)> value(raw, BN_free);
|
||||||
|
crypto::Bytes result(
|
||||||
|
static_cast<std::size_t>(BN_num_bytes(value.get()))
|
||||||
|
);
|
||||||
|
BN_bn2bin(value.get(), result.data());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, HashesAndHmacMatchKnownVectors) {
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::digest("SHA256", "abc"),
|
||||||
|
hex("ba7816bf8f01cfea414140de5dae2223"
|
||||||
|
"b00361a396177a9cb410ff61f20015ad")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::digest("SHA384", "abc"),
|
||||||
|
hex("cb00753f45a35e8bb5a03d699ac65007"
|
||||||
|
"272c32ab0eded1631a8b605a43ff5bed"
|
||||||
|
"8086072ba1e7cc2358baeca134c825a7")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::digest("SHA512", "abc"),
|
||||||
|
hex("ddaf35a193617abacc417349ae204131"
|
||||||
|
"12e6fa4e89a97ea20a9eeee64b55d39"
|
||||||
|
"a2192992a274fc1a836ba3c23a3feebbd"
|
||||||
|
"454d4423643ce80e2a9ac94fa54ca49f")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::digest("MD5", "abc"), hex("900150983cd24fb0d6963f7d28e17f72")
|
||||||
|
);
|
||||||
|
const crypto::Bytes key(20, 0x0b);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::hmac("SHA256", binary(key), "Hi There"),
|
||||||
|
hex("b0344c61d8db38535ca8afceaf0bf12b"
|
||||||
|
"881dc200c9833da726e9376c2e32cff7")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, StreamingHashCanBeResetAndRejectsUpdateAfterFinal) {
|
||||||
|
crypto::HashContext context("SHA256");
|
||||||
|
context.update("a");
|
||||||
|
context.update(std::string("b\0c", 3));
|
||||||
|
EXPECT_EQ(
|
||||||
|
context.final(), crypto::digest("SHA256", std::string("ab\0c", 4))
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
context.update("more");
|
||||||
|
FAIL() << "update after final must fail";
|
||||||
|
} catch (const crypto::Error& error) {
|
||||||
|
EXPECT_EQ(error.code(), crypto::ErrorCode::InvalidState);
|
||||||
|
}
|
||||||
|
context.reset();
|
||||||
|
context.update("abc");
|
||||||
|
EXPECT_EQ(context.final(), crypto::digest("SHA256", "abc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, Ed25519MatchesRfc8032Vector) {
|
||||||
|
auto publicKey =
|
||||||
|
hex("d75a980182b10ab7d54bfed3c964073a"
|
||||||
|
"0ee172f3daa62325af021a68f707511a");
|
||||||
|
auto signature =
|
||||||
|
hex("e5564300c360ac729086e2cc806e828a"
|
||||||
|
"84877f1eb8e5d974d873e06522490155"
|
||||||
|
"5fb8821590a33bacc61e39701cf9b46b"
|
||||||
|
"d25bf5f0595bbe24655141438e7a100b");
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::ed25519Verify(binary(publicKey), "", binary(signature))
|
||||||
|
);
|
||||||
|
signature[0] ^= 1;
|
||||||
|
EXPECT_FALSE(
|
||||||
|
crypto::ed25519Verify(binary(publicKey), "", binary(signature))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, Ed25519GeneratesKeysAndSigns) {
|
||||||
|
const auto keyPair = crypto::ed25519KeyPair();
|
||||||
|
ASSERT_EQ(keyPair.privateKey.size(), std::size_t {32});
|
||||||
|
ASSERT_EQ(keyPair.publicKey.size(), std::size_t {32});
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::ed25519Public(binary(keyPair.privateKey)), keyPair.publicKey
|
||||||
|
);
|
||||||
|
const auto signature =
|
||||||
|
crypto::ed25519Sign(binary(keyPair.privateKey), std::string("a\0b", 3));
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::ed25519Verify(
|
||||||
|
binary(keyPair.publicKey), std::string("a\0b", 3), binary(signature)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, EcdsaVerifiesAllSupportedCurves) {
|
||||||
|
struct Case {
|
||||||
|
const char* curve;
|
||||||
|
const char* opensslCurve;
|
||||||
|
const EVP_MD* digest;
|
||||||
|
const char* hash;
|
||||||
|
std::size_t coordinateSize;
|
||||||
|
};
|
||||||
|
const Case cases[] {
|
||||||
|
{"P-256", "prime256v1", EVP_sha256(), "SHA256", 32},
|
||||||
|
{"P-384", "secp384r1", EVP_sha384(), "SHA384", 48},
|
||||||
|
{"P-521", "secp521r1", EVP_sha512(), "SHA512", 66}
|
||||||
|
};
|
||||||
|
for (const auto& item : cases) {
|
||||||
|
SCOPED_TRACE(item.curve);
|
||||||
|
PkeyHandle key(
|
||||||
|
EVP_PKEY_Q_keygen(nullptr, nullptr, "EC", item.opensslCurve),
|
||||||
|
EVP_PKEY_free
|
||||||
|
);
|
||||||
|
ASSERT_NE(key, nullptr);
|
||||||
|
BIGNUM* rawX = nullptr;
|
||||||
|
BIGNUM* rawY = nullptr;
|
||||||
|
ASSERT_EQ(
|
||||||
|
EVP_PKEY_get_bn_param(key.get(), OSSL_PKEY_PARAM_EC_PUB_X, &rawX), 1
|
||||||
|
);
|
||||||
|
ASSERT_EQ(
|
||||||
|
EVP_PKEY_get_bn_param(key.get(), OSSL_PKEY_PARAM_EC_PUB_Y, &rawY), 1
|
||||||
|
);
|
||||||
|
std::unique_ptr<BIGNUM, decltype(&BN_free)> x(rawX, BN_free);
|
||||||
|
std::unique_ptr<BIGNUM, decltype(&BN_free)> y(rawY, BN_free);
|
||||||
|
crypto::Bytes publicKey(item.coordinateSize * 2 + 1);
|
||||||
|
publicKey[0] = 0x04;
|
||||||
|
ASSERT_EQ(
|
||||||
|
BN_bn2binpad(
|
||||||
|
x.get(), publicKey.data() + 1, item.coordinateSize
|
||||||
|
),
|
||||||
|
static_cast<int>(item.coordinateSize)
|
||||||
|
);
|
||||||
|
ASSERT_EQ(
|
||||||
|
BN_bn2binpad(
|
||||||
|
y.get(),
|
||||||
|
publicKey.data() + 1 + item.coordinateSize,
|
||||||
|
item.coordinateSize
|
||||||
|
),
|
||||||
|
static_cast<int>(item.coordinateSize)
|
||||||
|
);
|
||||||
|
auto signature = sign(key.get(), item.digest, "message");
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::ecdsaVerify(
|
||||||
|
item.curve,
|
||||||
|
binary(publicKey),
|
||||||
|
"message",
|
||||||
|
binary(signature),
|
||||||
|
item.hash
|
||||||
|
)
|
||||||
|
);
|
||||||
|
EXPECT_FALSE(
|
||||||
|
crypto::ecdsaVerify(
|
||||||
|
item.curve,
|
||||||
|
binary(publicKey),
|
||||||
|
"tampered",
|
||||||
|
binary(signature),
|
||||||
|
item.hash
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, EcdsaGeneratesKeysAndSignsOnAllSupportedCurves) {
|
||||||
|
struct Case {
|
||||||
|
const char* curve;
|
||||||
|
const char* hash;
|
||||||
|
std::size_t privateSize;
|
||||||
|
std::size_t publicSize;
|
||||||
|
};
|
||||||
|
const Case cases[] {
|
||||||
|
{"P-256", "SHA256", 32, 65},
|
||||||
|
{"P-384", "SHA384", 48, 97},
|
||||||
|
{"P-521", "SHA512", 66, 133}
|
||||||
|
};
|
||||||
|
for (const auto& item : cases) {
|
||||||
|
SCOPED_TRACE(item.curve);
|
||||||
|
const auto keyPair = crypto::ecdsaKeyPair(item.curve);
|
||||||
|
EXPECT_EQ(keyPair.privateKey.size(), item.privateSize);
|
||||||
|
EXPECT_EQ(keyPair.publicKey.size(), item.publicSize);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::ecdsaPublic(item.curve, binary(keyPair.privateKey)),
|
||||||
|
keyPair.publicKey
|
||||||
|
);
|
||||||
|
const auto signature = crypto::ecdsaSign(
|
||||||
|
item.curve, binary(keyPair.privateKey), "message", item.hash
|
||||||
|
);
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::ecdsaVerify(
|
||||||
|
item.curve,
|
||||||
|
binary(keyPair.publicKey),
|
||||||
|
"message",
|
||||||
|
binary(signature),
|
||||||
|
item.hash
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, RsaVerifiesPkcs1AndPssSignatures) {
|
||||||
|
PkeyHandle key(
|
||||||
|
EVP_PKEY_Q_keygen(nullptr, nullptr, "RSA", 2048), EVP_PKEY_free
|
||||||
|
);
|
||||||
|
ASSERT_NE(key, nullptr);
|
||||||
|
const auto modulus = keyInteger(key.get(), OSSL_PKEY_PARAM_RSA_N);
|
||||||
|
const auto exponent = keyInteger(key.get(), OSSL_PKEY_PARAM_RSA_E);
|
||||||
|
|
||||||
|
auto pkcs1 = sign(key.get(), EVP_sha256(), "message", RSA_PKCS1_PADDING);
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::rsaPkcs1Verify(
|
||||||
|
"SHA256",
|
||||||
|
binary(modulus),
|
||||||
|
binary(exponent),
|
||||||
|
"message",
|
||||||
|
binary(pkcs1)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
EXPECT_FALSE(
|
||||||
|
crypto::rsaPkcs1Verify(
|
||||||
|
"SHA256",
|
||||||
|
binary(modulus),
|
||||||
|
binary(exponent),
|
||||||
|
"tampered",
|
||||||
|
binary(pkcs1)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
auto pss = sign(
|
||||||
|
key.get(),
|
||||||
|
EVP_sha256(),
|
||||||
|
"message",
|
||||||
|
RSA_PKCS1_PSS_PADDING,
|
||||||
|
RSA_PSS_SALTLEN_DIGEST
|
||||||
|
);
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::rsaPssVerify(
|
||||||
|
"SHA256",
|
||||||
|
binary(modulus),
|
||||||
|
binary(exponent),
|
||||||
|
"message",
|
||||||
|
binary(pss),
|
||||||
|
-1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
EXPECT_FALSE(
|
||||||
|
crypto::rsaPssVerify(
|
||||||
|
"SHA256",
|
||||||
|
binary(modulus),
|
||||||
|
binary(exponent),
|
||||||
|
"tampered",
|
||||||
|
binary(pss),
|
||||||
|
-1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, X25519MatchesRfc7748PublicKeyVector) {
|
||||||
|
const auto privateKey =
|
||||||
|
hex("77076d0a7318a57d3c16c17251b26645"
|
||||||
|
"df4c2f87ebc0992ab177fba51db92c2a");
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::x25519Public(binary(privateKey)),
|
||||||
|
hex("8520f0098930a754748b7ddcb43ef75a0"
|
||||||
|
"dbf3a0d26381af4eba4a98eaa9b4e6a")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, X25519GeneratedKeyPairsDeriveTheSameSecret) {
|
||||||
|
const auto alice = crypto::x25519KeyPair();
|
||||||
|
const auto bob = crypto::x25519KeyPair();
|
||||||
|
EXPECT_EQ(alice.privateKey.size(), std::size_t {32});
|
||||||
|
EXPECT_EQ(alice.publicKey.size(), std::size_t {32});
|
||||||
|
EXPECT_EQ(crypto::x25519Public(binary(alice.privateKey)), alice.publicKey);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::x25519(binary(alice.privateKey), binary(bob.publicKey)),
|
||||||
|
crypto::x25519(binary(bob.privateKey), binary(alice.publicKey))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, P256DerivesPublicKeyAndSharedSecret) {
|
||||||
|
crypto::Bytes privateA(32, 0);
|
||||||
|
crypto::Bytes privateB(32, 0);
|
||||||
|
privateA.back() = 1;
|
||||||
|
privateB.back() = 2;
|
||||||
|
const auto publicA = crypto::p256Public(binary(privateA));
|
||||||
|
const auto publicB = crypto::p256Public(binary(privateB));
|
||||||
|
EXPECT_EQ(
|
||||||
|
publicA,
|
||||||
|
hex("046b17d1f2e12c4247f8bce6e563a440"
|
||||||
|
"f277037d812deb33a0f4a13945d898c296"
|
||||||
|
"4fe342e2fe1a7f9b8ee7eb4a7c0f9e16"
|
||||||
|
"2bce33576b315ececbb6406837bf51f5")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::p256Shared(binary(privateA), binary(publicB)),
|
||||||
|
crypto::p256Shared(binary(privateB), binary(publicA))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, AesGcmMatchesKnownVectorAndRejectsTampering) {
|
||||||
|
const crypto::Bytes key(16, 0);
|
||||||
|
const crypto::Bytes nonce(12, 0);
|
||||||
|
const crypto::Bytes plaintext(16, 0);
|
||||||
|
auto ciphertext = crypto::aesGcmEncrypt(
|
||||||
|
binary(key), binary(nonce), "", binary(plaintext)
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
ciphertext,
|
||||||
|
hex("0388dace60b6a392f328c2b971b2fe78"
|
||||||
|
"ab6e47d42cec13bdf53a67b21257bddf")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::aesGcmDecrypt(
|
||||||
|
binary(key), binary(nonce), "", binary(ciphertext)
|
||||||
|
),
|
||||||
|
plaintext
|
||||||
|
);
|
||||||
|
ciphertext.back() ^= 1;
|
||||||
|
EXPECT_THROW(
|
||||||
|
crypto::aesGcmDecrypt(
|
||||||
|
binary(key), binary(nonce), "", binary(ciphertext)
|
||||||
|
),
|
||||||
|
std::runtime_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, Chacha20Poly1305RoundTripsAndRejectsTampering) {
|
||||||
|
const crypto::Bytes key(32, 0);
|
||||||
|
const crypto::Bytes nonce(12, 0);
|
||||||
|
auto ciphertext = crypto::chacha20Poly1305Encrypt(
|
||||||
|
binary(key), binary(nonce), "aad", "plaintext"
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
binary(
|
||||||
|
crypto::chacha20Poly1305Decrypt(
|
||||||
|
binary(key), binary(nonce), "aad", binary(ciphertext)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"plaintext"
|
||||||
|
);
|
||||||
|
ciphertext.back() ^= 1;
|
||||||
|
EXPECT_THROW(
|
||||||
|
crypto::chacha20Poly1305Decrypt(
|
||||||
|
binary(key), binary(nonce), "aad", binary(ciphertext)
|
||||||
|
),
|
||||||
|
std::runtime_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, HkdfMatchesRfc5869Vector) {
|
||||||
|
const crypto::Bytes ikm(22, 0x0b);
|
||||||
|
const auto salt = hex("000102030405060708090a0b0c");
|
||||||
|
const auto info = hex("f0f1f2f3f4f5f6f7f8f9");
|
||||||
|
const auto prk = crypto::hkdfExtract("SHA256", binary(salt), binary(ikm));
|
||||||
|
EXPECT_EQ(
|
||||||
|
prk,
|
||||||
|
hex("077709362c2e32df0ddc3f0dc47bba63"
|
||||||
|
"90b6c73bb50f9c3122ec844ad7c2b3e5")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::hkdfExpand("SHA256", binary(prk), binary(info), 42),
|
||||||
|
hex("3cb25f25faacd57a90434f64d0362f2a"
|
||||||
|
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf"
|
||||||
|
"34007208d5b887185865")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, PasswordKdfsMatchKnownVectors) {
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::pbkdf2("SHA256", "password", "salt", 1, 32),
|
||||||
|
hex("120fb6cffcf8b32c43e7225256c4f837"
|
||||||
|
"a86548c92ccc35480805987cb70be17b")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::scrypt("", "", 16, 1, 1, 64),
|
||||||
|
hex("77d6576238657b203b19ca42c18a0497"
|
||||||
|
"f16b4844e3074ae8dfdffa3fede21442f"
|
||||||
|
"cd0069ded0948f8326a753a0fc81f17e"
|
||||||
|
"8d3e0fb2e0d3628cf35e20c38d18906")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, ReportsRuntimeCapabilitiesAndStableErrors) {
|
||||||
|
const auto available = crypto::features();
|
||||||
|
EXPECT_TRUE(available.sha256);
|
||||||
|
EXPECT_TRUE(available.ed25519);
|
||||||
|
EXPECT_TRUE(available.ecdsa);
|
||||||
|
EXPECT_TRUE(available.x25519);
|
||||||
|
EXPECT_TRUE(available.aes128Gcm);
|
||||||
|
EXPECT_TRUE(available.pbkdf2);
|
||||||
|
EXPECT_TRUE(available.scrypt);
|
||||||
|
EXPECT_EQ(crypto::API_VERSION, 1);
|
||||||
|
|
||||||
|
const crypto::Bytes key(16, 0);
|
||||||
|
const crypto::Bytes nonce(12, 0);
|
||||||
|
auto ciphertext =
|
||||||
|
crypto::aesGcmEncrypt(binary(key), binary(nonce), "", "secret");
|
||||||
|
ciphertext.back() ^= 1;
|
||||||
|
try {
|
||||||
|
crypto::aesGcmDecrypt(
|
||||||
|
binary(key), binary(nonce), "", binary(ciphertext)
|
||||||
|
);
|
||||||
|
FAIL() << "tampered ciphertext must fail";
|
||||||
|
} catch (const crypto::Error& error) {
|
||||||
|
EXPECT_EQ(error.code(), crypto::ErrorCode::AuthenticationFailed);
|
||||||
|
EXPECT_STREQ(error.codeName(), "authentication_failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, UtilitiesHandleBinaryData) {
|
||||||
|
const std::string binaryValue("a\0b", 3);
|
||||||
|
EXPECT_TRUE(crypto::constantTimeEqual(binaryValue, binaryValue));
|
||||||
|
EXPECT_FALSE(crypto::constantTimeEqual(binaryValue, "a"));
|
||||||
|
EXPECT_FALSE(crypto::constantTimeEqual("left", "lest"));
|
||||||
|
EXPECT_EQ(crypto::randomBytes(32).size(), std::size_t {32});
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
"entt",
|
"entt",
|
||||||
"gtest",
|
"gtest",
|
||||||
"curl",
|
"curl",
|
||||||
"freetype"
|
"freetype",
|
||||||
|
"openssl"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue