v2.2.2
This commit is contained in:
parent
f3e39fb8e6
commit
e5f5d32aab
124 changed files with 11892 additions and 2461 deletions
56
sdk/LICENSE
Normal file
56
sdk/LICENSE
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2026 Cristian Cezar Moisés <zupt@riseup.net>
|
||||
|
||||
libzuptsdk is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
libzuptsdk is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public
|
||||
License along with this program. If not, see:
|
||||
|
||||
https://www.gnu.org/licenses/agpl-3.0.txt
|
||||
https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
─────────────────────────────────────────────────────────────────────
|
||||
|
||||
ABOUT THIS LICENSE
|
||||
|
||||
The GNU Affero General Public License v3 (AGPLv3) is a copyleft
|
||||
license designed for software that may be run as a network service.
|
||||
It is identical to the GNU General Public License v3, with one
|
||||
additional requirement (Section 13): if you modify libzuptsdk and
|
||||
make the modified version available to users over a computer network,
|
||||
you must offer those users access to the corresponding modified
|
||||
source code.
|
||||
|
||||
This protects libzuptsdk against being adopted by SaaS providers as
|
||||
a private fork without contributing back, while keeping it freely
|
||||
usable by individuals, small businesses, and the broader open-source
|
||||
community.
|
||||
|
||||
If you write a separate program that is distributed alongside
|
||||
libzuptsdk (for example, statically linking it into your own
|
||||
application), the AGPL requires you to license that combined work
|
||||
under the AGPL as well — which means you must publish the source.
|
||||
If this is not acceptable for your use case, please contact the
|
||||
author for commercial licensing options:
|
||||
|
||||
zupt@riseup.net
|
||||
https://github.com/cristiancmoises/zupt
|
||||
|
||||
─────────────────────────────────────────────────────────────────────
|
||||
|
||||
The full text of the GNU Affero General Public License version 3
|
||||
should accompany this distribution as a separate file (or you may
|
||||
download it from the URLs above). It is approximately 35 KB / 619
|
||||
lines of plain text.
|
||||
141
sdk/Makefile.sdk
Normal file
141
sdk/Makefile.sdk
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# ─────────────────────────────────────────────────────────────────────
|
||||
# libzuptsdk — public C ABI for Zupt
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
SDK_VERSION_MAJOR = 1
|
||||
SDK_VERSION_MINOR = 0
|
||||
SDK_VERSION_PATCH = 0
|
||||
SDK_SOVERSION = $(SDK_VERSION_MAJOR)
|
||||
SDK_FULLVERSION = $(SDK_VERSION_MAJOR).$(SDK_VERSION_MINOR).$(SDK_VERSION_PATCH)
|
||||
|
||||
SDK_HDR = sdk/include/zuptsdk.h
|
||||
SDK_SRC = sdk/src/zuptsdk.c
|
||||
SDK_MAP = sdk/zuptsdk.map
|
||||
SDK_PREFIX ?= /usr/local
|
||||
|
||||
# All zupt sources except main.c get rebuilt with -fPIC for the SDK.
|
||||
# Object files go to sdk/build/ to avoid colliding with the CLI build.
|
||||
SDK_BUILD_DIR = sdk/build
|
||||
SDK_PIC_OBJS = $(patsubst src/%.c,$(SDK_BUILD_DIR)/%.o,$(filter-out src/zupt_main.c,$(ZUPT_SOURCES)))
|
||||
SDK_PIC_OBJS += $(patsubst src/%.c,$(SDK_BUILD_DIR)/%.o,$(VV_SOURCES))
|
||||
SDK_PIC_OBJS += $(SDK_BUILD_DIR)/zuptsdk.o
|
||||
|
||||
SDK_PIC_FLAGS = -fPIC -DZUPT_BUILDING_SDK=1
|
||||
|
||||
# VV files need SIMD flags too
|
||||
SDK_PIC_VV_FLAGS = $(SDK_PIC_FLAGS) $(VV_SIMD_FLAGS)
|
||||
|
||||
SDK_SHARED = sdk/build/libzuptsdk.so.$(SDK_FULLVERSION)
|
||||
SDK_SHARED_SO = sdk/build/libzuptsdk.so.$(SDK_SOVERSION)
|
||||
SDK_SHARED_LINK = sdk/build/libzuptsdk.so
|
||||
SDK_STATIC = sdk/build/libzuptsdk.a
|
||||
|
||||
SDK_PC = sdk/build/zuptsdk.pc
|
||||
|
||||
# Compile rule for SDK PIC objects (vv_* files need SIMD flags)
|
||||
$(SDK_BUILD_DIR)/vv_%.o: src/vv_%.c | $(SDK_BUILD_DIR)
|
||||
$(Q)$(CC) $(CFLAGS) $(SDK_PIC_VV_FLAGS) -I include -c $< -o $@
|
||||
|
||||
$(SDK_BUILD_DIR)/vaptvupt_api.o: src/vaptvupt_api.c | $(SDK_BUILD_DIR)
|
||||
$(Q)$(CC) $(CFLAGS) $(SDK_PIC_FLAGS) -I include -c $< -o $@
|
||||
|
||||
$(SDK_BUILD_DIR)/%.o: src/%.c | $(SDK_BUILD_DIR)
|
||||
$(Q)$(CC) $(CFLAGS) $(SDK_PIC_FLAGS) -I include -c $< -o $@
|
||||
|
||||
$(SDK_BUILD_DIR)/zuptsdk.o: $(SDK_SRC) $(SDK_HDR) | $(SDK_BUILD_DIR)
|
||||
$(Q)$(CC) $(CFLAGS) $(SDK_PIC_FLAGS) -I sdk/include -I include -I src -c $< -o $@
|
||||
|
||||
$(SDK_BUILD_DIR):
|
||||
$(Q)mkdir -p $(SDK_BUILD_DIR)
|
||||
|
||||
# Shared library
|
||||
$(SDK_SHARED): $(SDK_PIC_OBJS) $(SDK_MAP) $(JAZZ_O)
|
||||
@echo "[sdk-shared] $@"
|
||||
$(Q)$(CC) -shared -fPIC \
|
||||
-Wl,-soname,libzuptsdk.so.$(SDK_SOVERSION) \
|
||||
-Wl,--version-script,$(SDK_MAP) \
|
||||
$(LDFLAGS) \
|
||||
$(SDK_PIC_OBJS) $(JAZZ_O) \
|
||||
-o $@ $(LDLIBS)
|
||||
$(Q)cd $(SDK_BUILD_DIR) && ln -sf $(notdir $(SDK_SHARED)) libzuptsdk.so.$(SDK_SOVERSION)
|
||||
$(Q)cd $(SDK_BUILD_DIR) && ln -sf libzuptsdk.so.$(SDK_SOVERSION) libzuptsdk.so
|
||||
|
||||
# Static library
|
||||
$(SDK_STATIC): $(SDK_PIC_OBJS) $(JAZZ_O)
|
||||
@echo "[sdk-static] $@"
|
||||
$(Q)$(AR) rcs $@ $(SDK_PIC_OBJS) $(JAZZ_O)
|
||||
|
||||
# pkg-config file
|
||||
$(SDK_PC): $(SDK_HDR)
|
||||
@echo "[sdk-pc] $@"
|
||||
$(Q)mkdir -p $(SDK_BUILD_DIR)
|
||||
$(Q)printf 'prefix=$(SDK_PREFIX)\n' > $@
|
||||
$(Q)printf 'exec_prefix=$${prefix}\n' >> $@
|
||||
$(Q)printf 'libdir=$${exec_prefix}/lib\n' >> $@
|
||||
$(Q)printf 'includedir=$${prefix}/include\n\n' >> $@
|
||||
$(Q)printf 'Name: zuptsdk\n' >> $@
|
||||
$(Q)printf 'Description: Zupt backup compression SDK\n' >> $@
|
||||
$(Q)printf 'URL: https://git.securityops.co/cristiancmoises/zupt\n' >> $@
|
||||
$(Q)printf 'Version: $(SDK_FULLVERSION)\n' >> $@
|
||||
$(Q)printf 'Libs: -L$${libdir} -lzuptsdk\n' >> $@
|
||||
$(Q)printf 'Libs.private: -lpthread\n' >> $@
|
||||
$(Q)printf 'Cflags: -I$${includedir}\n' >> $@
|
||||
|
||||
# Convenience targets
|
||||
.PHONY: sdk sdk-shared sdk-static sdk-pkgconfig sdk-clean sdk-install \
|
||||
sdk-verify-symbols sdk-test
|
||||
|
||||
sdk: sdk-shared sdk-static sdk-pkgconfig
|
||||
|
||||
sdk-shared: $(SDK_SHARED)
|
||||
|
||||
sdk-static: $(SDK_STATIC)
|
||||
|
||||
sdk-pkgconfig: $(SDK_PC)
|
||||
|
||||
sdk-clean:
|
||||
$(Q)rm -rf $(SDK_BUILD_DIR)
|
||||
|
||||
# Symbol leakage verification.
|
||||
# Pass: every exported text symbol starts with `zuptsdk_`.
|
||||
# Fail: any symbol that doesn't.
|
||||
sdk-verify-symbols: $(SDK_SHARED)
|
||||
@echo "[sdk-verify] checking exported symbols in $(SDK_SHARED)"
|
||||
$(Q)leaked=$$(nm -D --defined-only $(SDK_SHARED) | grep ' T ' | awk '{print $$3}' | grep -v '^zuptsdk_' || true); \
|
||||
if [ -n "$$leaked" ]; then \
|
||||
echo "FAIL: non-zuptsdk symbols exported:"; \
|
||||
echo "$$leaked"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
expected=$$(grep -c '^ zuptsdk_' $(SDK_MAP)); \
|
||||
exported=$$(nm -D --defined-only $(SDK_SHARED) | grep ' T ' | grep -c '^.* T zuptsdk_' || true); \
|
||||
echo " $$exported exported / $$expected declared in version script"; \
|
||||
if [ "$$exported" -lt "$$expected" ]; then \
|
||||
echo "FAIL: $$((expected - exported)) declared symbols are missing from the .so"; \
|
||||
nm -D --defined-only $(SDK_SHARED) | grep ' T ' | grep '^.* T zuptsdk_' | awk '{print $$3}' | sort > /tmp/exp; \
|
||||
grep '^ zuptsdk_' $(SDK_MAP) | tr -d ' ;' | sort > /tmp/decl; \
|
||||
diff /tmp/decl /tmp/exp; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo " PASS: no symbol leakage, all declared symbols exported"
|
||||
|
||||
# Build & run roundtrip test
|
||||
sdk-test: $(SDK_SHARED)
|
||||
@echo "[sdk-test] building and running roundtrip"
|
||||
$(Q)$(CC) $(CFLAGS) -I sdk/include sdk/tests/test_sdk_roundtrip.c \
|
||||
-Lsdk/build -lzuptsdk \
|
||||
-Wl,-rpath,'$$ORIGIN/build' \
|
||||
-o sdk/build/test_sdk_roundtrip $(LDLIBS)
|
||||
$(Q)cd sdk && LD_LIBRARY_PATH=build ./build/test_sdk_roundtrip
|
||||
|
||||
sdk-install: sdk
|
||||
install -d $(DESTDIR)$(SDK_PREFIX)/lib
|
||||
install -d $(DESTDIR)$(SDK_PREFIX)/include
|
||||
install -d $(DESTDIR)$(SDK_PREFIX)/lib/pkgconfig
|
||||
install -m 0644 $(SDK_HDR) $(DESTDIR)$(SDK_PREFIX)/include/
|
||||
install -m 0755 $(SDK_SHARED) $(DESTDIR)$(SDK_PREFIX)/lib/
|
||||
cd $(DESTDIR)$(SDK_PREFIX)/lib && \
|
||||
ln -sf libzuptsdk.so.$(SDK_FULLVERSION) libzuptsdk.so.$(SDK_SOVERSION) && \
|
||||
ln -sf libzuptsdk.so.$(SDK_SOVERSION) libzuptsdk.so
|
||||
install -m 0644 $(SDK_STATIC) $(DESTDIR)$(SDK_PREFIX)/lib/
|
||||
install -m 0644 $(SDK_PC) $(DESTDIR)$(SDK_PREFIX)/lib/pkgconfig/
|
||||
183
sdk/README.md
Normal file
183
sdk/README.md
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
# libzuptsdk
|
||||
|
||||
Public C ABI for the [Zupt](https://git.securityops.co/cristiancmoises/zupt) backup compression library.
|
||||
|
||||
Provides post-quantum encrypted compression as a stable, embeddable shared library — completely independent of the `zupt` CLI.No dependency on any other compression library; everything is built from Zupt's own implementations.
|
||||
|
||||
- **Version:** 1.0.0
|
||||
- **License:** AGPL-3.0-or-later
|
||||
- **ABI:** Stable across 1.x via versioned symbols (`ZUPTSDK_1.0`)
|
||||
- **C standard:** Public header is C99; C11 implementation; works in C++17
|
||||
|
||||
## Features
|
||||
|
||||
- **Hybrid post-quantum encryption** — ML-KEM-768 + X25519 KEM
|
||||
- **Authenticated encryption** — AES-256-CTR + HMAC-SHA256, PBKDF2-SHA256 KDF
|
||||
- **Hardware-adaptive compression** — VaptVupt on AVX2/NEON, LZHP elsewhere
|
||||
- **Streaming I/O** — read/write callbacks for sockets, pipes, encrypted volumes
|
||||
- **Secure memory** — mlock-backed buffers for passwords and keys, zeroed on destroy
|
||||
- **Constant-time crypto** — Jasmin-verified assembly on x86_64
|
||||
- **Per-context state** — no globals; safe to use from any thread on distinct contexts
|
||||
- **Custom allocator hooks** — embed cleanly in any runtime
|
||||
|
||||
## Quick start (C)
|
||||
|
||||
```c
|
||||
#include <zuptsdk.h>
|
||||
|
||||
zuptsdk_ctx_t *ctx;
|
||||
zuptsdk_ctx_create(&ctx);
|
||||
|
||||
zuptsdk_secure_buf_t *pw;
|
||||
zuptsdk_secure_buf_from_data((const uint8_t *)"mypassword", 10, &pw);
|
||||
|
||||
uint8_t *archive;
|
||||
size_t archive_sz;
|
||||
zuptsdk_compress_buffer(ctx, NULL, "data.bin",
|
||||
my_data, my_data_size,
|
||||
pw, NULL,
|
||||
&archive, &archive_sz);
|
||||
|
||||
/* ... store archive somewhere ... */
|
||||
|
||||
uint8_t *out;
|
||||
size_t out_sz;
|
||||
zuptsdk_extract_buffer(ctx, archive, archive_sz, pw, NULL, &out, &out_sz);
|
||||
|
||||
zuptsdk_free(archive);
|
||||
zuptsdk_free(out);
|
||||
zuptsdk_secure_buf_destroy(pw);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
```
|
||||
|
||||
Build with `pkg-config`:
|
||||
|
||||
```sh
|
||||
gcc myapp.c $(pkg-config --cflags --libs zuptsdk) -o myapp
|
||||
```
|
||||
|
||||
## Quick start (Python)
|
||||
|
||||
```python
|
||||
import zuptsdk
|
||||
|
||||
with zuptsdk.Context() as ctx:
|
||||
archive = ctx.compress_buffer(b"hello world",
|
||||
name="hello.txt",
|
||||
password=b"secret")
|
||||
|
||||
data = ctx.extract_buffer(archive, password=b"secret")
|
||||
assert data == b"hello world"
|
||||
```
|
||||
|
||||
Post-quantum mode:
|
||||
|
||||
```python
|
||||
with zuptsdk.Context() as ctx:
|
||||
kp = ctx.generate_keypair()
|
||||
kp.save("/tmp/mykey") # writes mykey.key (priv, 0600) + mykey.pub (pub, 0644)
|
||||
|
||||
archive = ctx.compress_buffer(b"secret data",
|
||||
name="s.txt",
|
||||
public_key=kp.public)
|
||||
|
||||
data = ctx.extract_buffer(archive, private_key=kp.private)
|
||||
```
|
||||
|
||||
## Build & install
|
||||
|
||||
```sh
|
||||
git clone https://git.securityops.co/cristiancmoises/zupt
|
||||
cd zupt
|
||||
make # builds CLI (required: produces jasmin/*.o assembly objects)
|
||||
make sdk # builds libzuptsdk.so.1.0.0 + libzuptsdk.a + zuptsdk.pc
|
||||
make sdk-test # runs C roundtrip suite (15 tests)
|
||||
sudo make sdk-install PREFIX=/usr/local
|
||||
```
|
||||
|
||||
This installs:
|
||||
- `/usr/local/include/zuptsdk.h`
|
||||
- `/usr/local/lib/libzuptsdk.so.1.0.0` (with versioned `.so.1` and `.so` symlinks)
|
||||
- `/usr/local/lib/libzuptsdk.a`
|
||||
- `/usr/local/lib/pkgconfig/zuptsdk.pc`
|
||||
|
||||
## Symbol visibility
|
||||
|
||||
The shared library exports **only** the 55 documented public symbols, all prefixed with `zuptsdk_`. Internal `zupt_*` and `vv_*` symbols are hidden via a linker version script.
|
||||
|
||||
Verify yourself:
|
||||
|
||||
```sh
|
||||
make sdk-verify-symbols
|
||||
# [sdk-verify] checking exported symbols in sdk/build/libzuptsdk.so.1.0.0
|
||||
# 55 exported / 55 declared in version script
|
||||
# PASS: no symbol leakage, all declared symbols exported
|
||||
```
|
||||
|
||||
## ABI stability policy
|
||||
|
||||
Every symbol declared in `zuptsdk.h` is part of the stable v1.0 ABI and is gated under the linker tag `ZUPTSDK_1.0`.
|
||||
|
||||
- **New symbols** in v1.x get added under new tags (`ZUPTSDK_1.1`, ...)
|
||||
- **Existing symbols** never change signature within v1.x
|
||||
- **Breaking changes** require a major bump (`libzuptsdk.so.2`)
|
||||
|
||||
Do not link against internal `zupt_*` symbols even if you find them in the static archive — they will disappear without notice.
|
||||
|
||||
## Thread safety
|
||||
|
||||
- Concurrent calls on **distinct contexts** are safe (MT-Safe).
|
||||
- Concurrent calls on the **same context** are not safe.
|
||||
|
||||
For parallel work, create one context per worker thread.
|
||||
|
||||
## Memory ownership
|
||||
|
||||
Every output pointer documents its destroyer. Always use the documented function (`zuptsdk_free`, `zuptsdk_*_destroy`) to release memory — never bare `free()` — because the library may have been built with a custom allocator.
|
||||
|
||||
Function parameter conventions:
|
||||
- `[in]` — caller owns, library reads only
|
||||
- `[out]` — caller owns, library writes
|
||||
- `[in,out]` — caller owns, library reads and writes
|
||||
- `[transfers]` — ownership moves caller ↔ library
|
||||
- `[borrowed]` — pointer valid only for the duration of the call
|
||||
|
||||
## Error handling
|
||||
|
||||
Functions return `int` where 0 = success, negative = `zuptsdk_error_t` code.
|
||||
|
||||
```c
|
||||
int rc = zuptsdk_compress_buffer(...);
|
||||
if (rc != ZUPTSDK_OK) {
|
||||
fprintf(stderr, "%s\n", zuptsdk_strerror(rc));
|
||||
fprintf(stderr, " detail: %s\n", zuptsdk_last_error_detail());
|
||||
return rc;
|
||||
}
|
||||
```
|
||||
|
||||
`zuptsdk_last_error_detail()` returns a thread-local string with file:line context.
|
||||
|
||||
## Components
|
||||
|
||||
```
|
||||
sdk/
|
||||
├── include/zuptsdk.h # Public C99 header (55 functions, 7 opaque types)
|
||||
├── src/zuptsdk.c # Implementation (wraps zupt internals)
|
||||
├── zuptsdk.map # Linker version script (gates exports)
|
||||
├── Makefile.sdk # Build integration
|
||||
├── bindings/python/zuptsdk.py # Python cffi reference bindings
|
||||
├── tests/test_sdk_roundtrip.c # C roundtrip suite
|
||||
└── tests/test_python.py # Python test suite
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
libzuptsdk is licensed under **AGPL-3.0-or-later** (see `sdk/LICENSE`).
|
||||
|
||||
The AGPL allows everyone to use the library freely, but anyone running it as a network service must publish their source code modifications. This protects the project from enterprise exploitation while keeping it usable by individuals, small businesses, and the open-source community.
|
||||
|
||||
## Contact
|
||||
|
||||
- Repository: https://git.securityops.co/cristiancmoises/zupt
|
||||
- Website: https://zupt.securityops.co
|
||||
- Email: zupt@riseup.net
|
||||
BIN
sdk/bindings/python/__pycache__/zuptsdk.cpython-312.pyc
Normal file
BIN
sdk/bindings/python/__pycache__/zuptsdk.cpython-312.pyc
Normal file
Binary file not shown.
468
sdk/bindings/python/zuptsdk.py
Normal file
468
sdk/bindings/python/zuptsdk.py
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
"""
|
||||
zuptsdk — Python bindings for libzuptsdk
|
||||
|
||||
Copyright (c) 2026 Cristian Cezar Moisés
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
Reference Python bindings generated via cffi. Provides a Pythonic API
|
||||
on top of the C ABI; secrets (passwords, keys) are managed in
|
||||
mlock-backed buffers that zero on garbage collection.
|
||||
|
||||
Quick start:
|
||||
import zuptsdk
|
||||
with zuptsdk.Context() as ctx:
|
||||
archive = ctx.compress_buffer(b"hello world", name="hello.txt",
|
||||
password=b"secret")
|
||||
data = ctx.extract_buffer(archive, password=b"secret")
|
||||
assert data == b"hello world"
|
||||
|
||||
Post-quantum:
|
||||
with zuptsdk.Context() as ctx:
|
||||
keypair = ctx.generate_keypair()
|
||||
keypair.save("/tmp/mykey")
|
||||
archive = ctx.compress_buffer(b"hello", name="h.txt",
|
||||
public_key=keypair.public)
|
||||
data = ctx.extract_buffer(archive, private_key=keypair.private)
|
||||
"""
|
||||
|
||||
import os
|
||||
from cffi import FFI
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# CFFI setup
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
_ffi = FFI()
|
||||
_ffi.cdef("""
|
||||
typedef struct zuptsdk_ctx zuptsdk_ctx_t;
|
||||
typedef struct zuptsdk_options zuptsdk_options_t;
|
||||
typedef struct zuptsdk_archive_info zuptsdk_archive_info_t;
|
||||
typedef struct zuptsdk_secure_buf zuptsdk_secure_buf_t;
|
||||
typedef struct zuptsdk_keypair zuptsdk_keypair_t;
|
||||
typedef struct zuptsdk_pubkey zuptsdk_pubkey_t;
|
||||
typedef struct zuptsdk_privkey zuptsdk_privkey_t;
|
||||
|
||||
const char *zuptsdk_version_string(void);
|
||||
int zuptsdk_version_check(int major, int minor, int patch);
|
||||
const char *zuptsdk_strerror(int err);
|
||||
const char *zuptsdk_last_error_detail(void);
|
||||
|
||||
int zuptsdk_ctx_create(zuptsdk_ctx_t **ctx_out);
|
||||
void zuptsdk_ctx_destroy(zuptsdk_ctx_t *ctx);
|
||||
int zuptsdk_ctx_set_threads(zuptsdk_ctx_t *ctx, int threads);
|
||||
|
||||
int zuptsdk_options_create(zuptsdk_options_t **opts_out);
|
||||
void zuptsdk_options_destroy(zuptsdk_options_t *opts);
|
||||
int zuptsdk_options_set_codec(zuptsdk_options_t *opts, int codec);
|
||||
int zuptsdk_options_set_level(zuptsdk_options_t *opts, int level);
|
||||
int zuptsdk_options_set_dedup(zuptsdk_options_t *opts, int enabled);
|
||||
int zuptsdk_options_set_solid(zuptsdk_options_t *opts, int enabled);
|
||||
|
||||
int zuptsdk_secure_buf_create(size_t size, zuptsdk_secure_buf_t **buf_out);
|
||||
void zuptsdk_secure_buf_destroy(zuptsdk_secure_buf_t *buf);
|
||||
int zuptsdk_secure_buf_get(zuptsdk_secure_buf_t *buf,
|
||||
uint8_t **data_out, size_t *size_out);
|
||||
int zuptsdk_secure_buf_from_data(const uint8_t *data, size_t size,
|
||||
zuptsdk_secure_buf_t **buf_out);
|
||||
|
||||
int zuptsdk_keypair_generate(zuptsdk_ctx_t *ctx, zuptsdk_keypair_t **kp_out);
|
||||
void zuptsdk_keypair_destroy(zuptsdk_keypair_t *kp);
|
||||
int zuptsdk_keypair_save_private(const zuptsdk_keypair_t *kp, const char *path);
|
||||
int zuptsdk_keypair_save_public(const zuptsdk_keypair_t *kp, const char *path);
|
||||
int zuptsdk_privkey_load(const char *path, zuptsdk_privkey_t **key_out);
|
||||
void zuptsdk_privkey_destroy(zuptsdk_privkey_t *key);
|
||||
int zuptsdk_pubkey_load(const char *path, zuptsdk_pubkey_t **key_out);
|
||||
void zuptsdk_pubkey_destroy(zuptsdk_pubkey_t *key);
|
||||
|
||||
int zuptsdk_compress_buffer(zuptsdk_ctx_t *ctx,
|
||||
const zuptsdk_options_t *opts,
|
||||
const char *logical_name,
|
||||
const uint8_t *data, size_t data_sz,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_pubkey_t *recipient_pk,
|
||||
uint8_t **archive_out, size_t *archive_sz);
|
||||
|
||||
int zuptsdk_extract_buffer(zuptsdk_ctx_t *ctx,
|
||||
const uint8_t *archive, size_t archive_sz,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_privkey_t *recipient_sk,
|
||||
uint8_t **data_out, size_t *data_sz);
|
||||
|
||||
int zuptsdk_extract_to_dir(zuptsdk_ctx_t *ctx,
|
||||
const uint8_t *archive, size_t archive_sz,
|
||||
const char *dest_dir,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_privkey_t *recipient_sk);
|
||||
|
||||
int zuptsdk_verify(zuptsdk_ctx_t *ctx,
|
||||
const uint8_t *archive, size_t archive_sz,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_privkey_t *recipient_sk);
|
||||
|
||||
int zuptsdk_archive_info_read(zuptsdk_ctx_t *ctx,
|
||||
const uint8_t *archive, size_t archive_sz,
|
||||
zuptsdk_archive_info_t **info_out);
|
||||
void zuptsdk_archive_info_destroy(zuptsdk_archive_info_t *info);
|
||||
int zuptsdk_archive_info_format_major(const zuptsdk_archive_info_t *i);
|
||||
int zuptsdk_archive_info_format_minor(const zuptsdk_archive_info_t *i);
|
||||
const char *zuptsdk_archive_info_uuid(const zuptsdk_archive_info_t *i);
|
||||
int64_t zuptsdk_archive_info_created_unix(const zuptsdk_archive_info_t *i);
|
||||
uint64_t zuptsdk_archive_info_size(const zuptsdk_archive_info_t *i);
|
||||
int zuptsdk_archive_info_is_encrypted(const zuptsdk_archive_info_t *i);
|
||||
int zuptsdk_archive_info_is_pq_hybrid(const zuptsdk_archive_info_t *i);
|
||||
int zuptsdk_archive_info_is_solid(const zuptsdk_archive_info_t *i);
|
||||
int zuptsdk_archive_info_is_dedup(const zuptsdk_archive_info_t *i);
|
||||
|
||||
void zuptsdk_free(void *ptr);
|
||||
""")
|
||||
|
||||
# Try multiple paths to find the library
|
||||
def _load_library():
|
||||
candidates = [
|
||||
os.environ.get("ZUPTSDK_LIBRARY"),
|
||||
"libzuptsdk.so.1",
|
||||
"libzuptsdk.so",
|
||||
"/usr/local/lib/libzuptsdk.so.1",
|
||||
"/usr/lib/libzuptsdk.so.1",
|
||||
]
|
||||
# Also try ../build relative to this file (dev mode)
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
candidates.append(os.path.join(here, "..", "..", "build", "libzuptsdk.so.1"))
|
||||
candidates.append(os.path.join(here, "..", "..", "build", "libzuptsdk.so"))
|
||||
|
||||
last_err = None
|
||||
for c in candidates:
|
||||
if not c:
|
||||
continue
|
||||
try:
|
||||
return _ffi.dlopen(c)
|
||||
except OSError as e:
|
||||
last_err = e
|
||||
raise OSError(f"Could not load libzuptsdk.so.1 ({last_err}). "
|
||||
f"Set ZUPTSDK_LIBRARY env var or install the library.")
|
||||
|
||||
_lib = _load_library()
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Constants
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
class Codec:
|
||||
AUTO = 0
|
||||
VAPTVUPT = 1
|
||||
LZHP = 2
|
||||
LZH = 3
|
||||
LZ = 4
|
||||
STORE = 5
|
||||
|
||||
|
||||
class _Errors:
|
||||
OK = 0
|
||||
INVALID_ARG = -1
|
||||
NO_MEMORY = -2
|
||||
IO = -3
|
||||
BAD_ARCHIVE = -4
|
||||
BAD_PASSWORD = -5
|
||||
BAD_KEY = -6
|
||||
BAD_MAC = -7
|
||||
BAD_VERSION = -8
|
||||
BAD_CHECKSUM = -9
|
||||
BUFFER_TOO_SMALL = -10
|
||||
NOT_ENCRYPTED = -11
|
||||
PASSWORD_REQUIRED = -12
|
||||
PQ_KEY_REQUIRED = -13
|
||||
UNSUPPORTED = -14
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Exceptions
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
class ZuptError(Exception):
|
||||
"""Base exception for any libzuptsdk error."""
|
||||
def __init__(self, code, detail=None):
|
||||
self.code = code
|
||||
msg = _ffi.string(_lib.zuptsdk_strerror(code)).decode("utf-8", "replace")
|
||||
if detail:
|
||||
msg = f"{msg}: {detail}"
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class BadPassword(ZuptError): pass
|
||||
class BadKey(ZuptError): pass
|
||||
class BadArchive(ZuptError): pass
|
||||
class PasswordRequired(ZuptError): pass
|
||||
class PQKeyRequired(ZuptError): pass
|
||||
|
||||
|
||||
def _check(rc):
|
||||
"""Raise the right exception for any non-OK return code."""
|
||||
if rc == _Errors.OK:
|
||||
return
|
||||
detail = _ffi.string(_lib.zuptsdk_last_error_detail()).decode("utf-8", "replace")
|
||||
if rc == _Errors.BAD_PASSWORD: raise BadPassword(rc, detail)
|
||||
if rc == _Errors.BAD_KEY: raise BadKey(rc, detail)
|
||||
if rc == _Errors.BAD_ARCHIVE: raise BadArchive(rc, detail)
|
||||
if rc == _Errors.PASSWORD_REQUIRED: raise PasswordRequired(rc, detail)
|
||||
if rc == _Errors.PQ_KEY_REQUIRED: raise PQKeyRequired(rc, detail)
|
||||
raise ZuptError(rc, detail)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# SecureBuf — helper to wrap a Python bytes into an mlock'd buffer
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
class SecureBuf:
|
||||
"""A secure buffer for passwords or key material. Memory is mlock'd
|
||||
and zeroed on close. Use as a context manager."""
|
||||
|
||||
def __init__(self, data):
|
||||
if not isinstance(data, (bytes, bytearray)):
|
||||
raise TypeError("SecureBuf takes bytes")
|
||||
self._buf_pp = _ffi.new("zuptsdk_secure_buf_t **")
|
||||
rc = _lib.zuptsdk_secure_buf_from_data(data, len(data), self._buf_pp)
|
||||
_check(rc)
|
||||
self._buf = self._buf_pp[0]
|
||||
|
||||
def _handle(self):
|
||||
return self._buf
|
||||
|
||||
def close(self):
|
||||
if self._buf:
|
||||
_lib.zuptsdk_secure_buf_destroy(self._buf)
|
||||
self._buf = _ffi.NULL
|
||||
|
||||
def __enter__(self): return self
|
||||
def __exit__(self, *a): self.close()
|
||||
def __del__(self):
|
||||
try: self.close()
|
||||
except Exception: pass
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Keypair, PublicKey, PrivateKey
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
class PublicKey:
|
||||
def __init__(self, path):
|
||||
kp = _ffi.new("zuptsdk_pubkey_t **")
|
||||
_check(_lib.zuptsdk_pubkey_load(path.encode("utf-8"), kp))
|
||||
self._key = kp[0]
|
||||
def _handle(self): return self._key
|
||||
def __del__(self):
|
||||
try:
|
||||
if self._key:
|
||||
_lib.zuptsdk_pubkey_destroy(self._key)
|
||||
self._key = _ffi.NULL
|
||||
except Exception: pass
|
||||
|
||||
|
||||
class PrivateKey:
|
||||
def __init__(self, path):
|
||||
kp = _ffi.new("zuptsdk_privkey_t **")
|
||||
_check(_lib.zuptsdk_privkey_load(path.encode("utf-8"), kp))
|
||||
self._key = kp[0]
|
||||
def _handle(self): return self._key
|
||||
def __del__(self):
|
||||
try:
|
||||
if self._key:
|
||||
_lib.zuptsdk_privkey_destroy(self._key)
|
||||
self._key = _ffi.NULL
|
||||
except Exception: pass
|
||||
|
||||
|
||||
class Keypair:
|
||||
"""Generated PQ hybrid keypair. Wraps internal temporary files;
|
||||
use save() to persist the key material."""
|
||||
|
||||
def __init__(self, ctx_handle):
|
||||
kp = _ffi.new("zuptsdk_keypair_t **")
|
||||
_check(_lib.zuptsdk_keypair_generate(ctx_handle, kp))
|
||||
self._kp = kp[0]
|
||||
self._priv_path = None
|
||||
self._pub_path = None
|
||||
|
||||
def save(self, base_path):
|
||||
"""Save keypair as <base_path>.key (private) and <base_path>.pub.
|
||||
Private key is written with mode 0600."""
|
||||
priv = base_path + ".key"
|
||||
pub = base_path + ".pub"
|
||||
_check(_lib.zuptsdk_keypair_save_private(self._kp, priv.encode("utf-8")))
|
||||
_check(_lib.zuptsdk_keypair_save_public(self._kp, pub.encode("utf-8")))
|
||||
self._priv_path, self._pub_path = priv, pub
|
||||
return priv, pub
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
if not self._pub_path:
|
||||
raise RuntimeError("save() the keypair before accessing keys")
|
||||
return PublicKey(self._pub_path)
|
||||
|
||||
@property
|
||||
def private(self):
|
||||
if not self._priv_path:
|
||||
raise RuntimeError("save() the keypair before accessing keys")
|
||||
return PrivateKey(self._priv_path)
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
if self._kp:
|
||||
_lib.zuptsdk_keypair_destroy(self._kp)
|
||||
self._kp = _ffi.NULL
|
||||
except Exception: pass
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# ArchiveInfo
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
class ArchiveInfo:
|
||||
def __init__(self, handle):
|
||||
self._h = handle
|
||||
# Snapshot fields immediately so handle can be freed
|
||||
self.format_major = int(_lib.zuptsdk_archive_info_format_major(handle))
|
||||
self.format_minor = int(_lib.zuptsdk_archive_info_format_minor(handle))
|
||||
uuid_p = _lib.zuptsdk_archive_info_uuid(handle)
|
||||
self.uuid = _ffi.string(uuid_p).decode("utf-8") if uuid_p else ""
|
||||
self.created_unix = int(_lib.zuptsdk_archive_info_created_unix(handle))
|
||||
self.size = int(_lib.zuptsdk_archive_info_size(handle))
|
||||
self.is_encrypted = bool(_lib.zuptsdk_archive_info_is_encrypted(handle))
|
||||
self.is_pq_hybrid = bool(_lib.zuptsdk_archive_info_is_pq_hybrid(handle))
|
||||
self.is_solid = bool(_lib.zuptsdk_archive_info_is_solid(handle))
|
||||
self.is_dedup = bool(_lib.zuptsdk_archive_info_is_dedup(handle))
|
||||
_lib.zuptsdk_archive_info_destroy(handle)
|
||||
self._h = None
|
||||
|
||||
def __repr__(self):
|
||||
return (f"<ArchiveInfo v{self.format_major}.{self.format_minor} "
|
||||
f"uuid={self.uuid} encrypted={self.is_encrypted} "
|
||||
f"pq={self.is_pq_hybrid} size={self.size}>")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Options
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
class _Options:
|
||||
def __init__(self, codec=Codec.AUTO, level=7, dedup=False, solid=False):
|
||||
op = _ffi.new("zuptsdk_options_t **")
|
||||
_check(_lib.zuptsdk_options_create(op))
|
||||
self._o = op[0]
|
||||
_check(_lib.zuptsdk_options_set_codec(self._o, codec))
|
||||
_check(_lib.zuptsdk_options_set_level(self._o, level))
|
||||
_check(_lib.zuptsdk_options_set_dedup(self._o, 1 if dedup else 0))
|
||||
_check(_lib.zuptsdk_options_set_solid(self._o, 1 if solid else 0))
|
||||
|
||||
def _handle(self): return self._o
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
if self._o:
|
||||
_lib.zuptsdk_options_destroy(self._o)
|
||||
self._o = _ffi.NULL
|
||||
except Exception: pass
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Context — main entry point
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
class Context:
|
||||
"""SDK context. Holds thread pool, callbacks, error state."""
|
||||
|
||||
def __init__(self, threads=0):
|
||||
cp = _ffi.new("zuptsdk_ctx_t **")
|
||||
_check(_lib.zuptsdk_ctx_create(cp))
|
||||
self._ctx = cp[0]
|
||||
if threads:
|
||||
_check(_lib.zuptsdk_ctx_set_threads(self._ctx, threads))
|
||||
|
||||
def __enter__(self): return self
|
||||
def __exit__(self, *a): self.close()
|
||||
|
||||
def close(self):
|
||||
if self._ctx:
|
||||
_lib.zuptsdk_ctx_destroy(self._ctx)
|
||||
self._ctx = _ffi.NULL
|
||||
|
||||
def __del__(self):
|
||||
try: self.close()
|
||||
except Exception: pass
|
||||
|
||||
# ──── helpers ────
|
||||
def _password(self, pw):
|
||||
"""Coerce bytes/SecureBuf/None -> handle."""
|
||||
if pw is None: return _ffi.NULL
|
||||
if isinstance(pw, SecureBuf): return pw._handle()
|
||||
if isinstance(pw, (bytes, bytearray)):
|
||||
self._tmp_pw = SecureBuf(bytes(pw))
|
||||
return self._tmp_pw._handle()
|
||||
raise TypeError("password must be bytes, SecureBuf, or None")
|
||||
|
||||
# ──── operations ────
|
||||
def compress_buffer(self, data, name="data.bin",
|
||||
password=None, public_key=None,
|
||||
codec=Codec.AUTO, level=7, dedup=False, solid=False):
|
||||
"""Compress a single in-memory buffer. Returns archive bytes."""
|
||||
if not isinstance(data, (bytes, bytearray)):
|
||||
raise TypeError("data must be bytes")
|
||||
opts = _Options(codec=codec, level=level, dedup=dedup, solid=solid)
|
||||
pw_h = self._password(password)
|
||||
pk_h = public_key._handle() if public_key else _ffi.NULL
|
||||
|
||||
out_p = _ffi.new("uint8_t **")
|
||||
out_sz = _ffi.new("size_t *")
|
||||
_check(_lib.zuptsdk_compress_buffer(self._ctx, opts._handle(),
|
||||
name.encode("utf-8"),
|
||||
data, len(data),
|
||||
pw_h, pk_h, out_p, out_sz))
|
||||
result = bytes(_ffi.buffer(out_p[0], out_sz[0]))
|
||||
_lib.zuptsdk_free(out_p[0])
|
||||
return result
|
||||
|
||||
def extract_buffer(self, archive, password=None, private_key=None):
|
||||
"""Extract a single-file archive back to bytes."""
|
||||
if not isinstance(archive, (bytes, bytearray)):
|
||||
raise TypeError("archive must be bytes")
|
||||
pw_h = self._password(password)
|
||||
sk_h = private_key._handle() if private_key else _ffi.NULL
|
||||
|
||||
out_p = _ffi.new("uint8_t **")
|
||||
out_sz = _ffi.new("size_t *")
|
||||
_check(_lib.zuptsdk_extract_buffer(self._ctx, archive, len(archive),
|
||||
pw_h, sk_h, out_p, out_sz))
|
||||
result = bytes(_ffi.buffer(out_p[0], out_sz[0]))
|
||||
_lib.zuptsdk_free(out_p[0])
|
||||
return result
|
||||
|
||||
def extract_to_dir(self, archive, dest_dir, password=None, private_key=None):
|
||||
if not isinstance(archive, (bytes, bytearray)):
|
||||
raise TypeError("archive must be bytes")
|
||||
pw_h = self._password(password)
|
||||
sk_h = private_key._handle() if private_key else _ffi.NULL
|
||||
_check(_lib.zuptsdk_extract_to_dir(self._ctx, archive, len(archive),
|
||||
dest_dir.encode("utf-8"),
|
||||
pw_h, sk_h))
|
||||
|
||||
def verify(self, archive, password=None, private_key=None):
|
||||
"""Verify an archive's integrity. Returns True or raises."""
|
||||
pw_h = self._password(password)
|
||||
sk_h = private_key._handle() if private_key else _ffi.NULL
|
||||
_check(_lib.zuptsdk_verify(self._ctx, archive, len(archive), pw_h, sk_h))
|
||||
return True
|
||||
|
||||
def info(self, archive):
|
||||
"""Read archive header metadata. No password/key needed."""
|
||||
info_p = _ffi.new("zuptsdk_archive_info_t **")
|
||||
_check(_lib.zuptsdk_archive_info_read(self._ctx, archive, len(archive), info_p))
|
||||
return ArchiveInfo(info_p[0])
|
||||
|
||||
def generate_keypair(self):
|
||||
return Keypair(self._ctx)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# Module info
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
__version__ = _ffi.string(_lib.zuptsdk_version_string()).decode("utf-8")
|
||||
80
sdk/doc/example.c
Normal file
80
sdk/doc/example.c
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* libzuptsdk example: compress a file with a password, then extract it.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*
|
||||
* Build: gcc example.c $(pkg-config --cflags --libs zuptsdk) -o example
|
||||
* # or, before installation:
|
||||
* gcc example.c -I sdk/include -L sdk/build -lzuptsdk \
|
||||
* -Wl,-rpath,'$ORIGIN/../build' -o example
|
||||
*/
|
||||
#include <zuptsdk.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int die(const char *what, int rc) {
|
||||
fprintf(stderr, "ERROR (%s): %s\n", what, zuptsdk_strerror(rc));
|
||||
fprintf(stderr, " detail: %s\n", zuptsdk_last_error_detail());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
/* 1. Always check ABI compatibility at startup */
|
||||
int rc = zuptsdk_version_check(1, 0, 0);
|
||||
if (rc) return die("version_check", rc);
|
||||
printf("libzuptsdk %s\n\n", zuptsdk_version_string());
|
||||
|
||||
/* 2. Create a context (cheap; one per worker thread is fine) */
|
||||
zuptsdk_ctx_t *ctx = NULL;
|
||||
rc = zuptsdk_ctx_create(&ctx);
|
||||
if (rc) return die("ctx_create", rc);
|
||||
|
||||
/* 3. Wrap a password in a secure buffer (mlock + zero on destroy) */
|
||||
zuptsdk_secure_buf_t *pw = NULL;
|
||||
const char *password = "correct horse battery staple";
|
||||
rc = zuptsdk_secure_buf_from_data((const uint8_t *)password,
|
||||
strlen(password), &pw);
|
||||
if (rc) { zuptsdk_ctx_destroy(ctx); return die("secure_buf", rc); }
|
||||
|
||||
/* 4. Compress some data into a memory archive */
|
||||
const char *plaintext = "This is the secret data we want to protect.\n";
|
||||
uint8_t *archive = NULL;
|
||||
size_t archive_sz = 0;
|
||||
rc = zuptsdk_compress_buffer(ctx, NULL,
|
||||
"secret.txt",
|
||||
(const uint8_t *)plaintext, strlen(plaintext),
|
||||
pw, NULL,
|
||||
&archive, &archive_sz);
|
||||
if (rc) {
|
||||
zuptsdk_secure_buf_destroy(pw);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
return die("compress_buffer", rc);
|
||||
}
|
||||
printf("Compressed %zu bytes -> %zu byte archive\n", strlen(plaintext), archive_sz);
|
||||
|
||||
/* 5. Extract it back, verifying byte-for-byte */
|
||||
uint8_t *extracted = NULL;
|
||||
size_t extracted_sz = 0;
|
||||
rc = zuptsdk_extract_buffer(ctx, archive, archive_sz,
|
||||
pw, NULL,
|
||||
&extracted, &extracted_sz);
|
||||
if (rc) {
|
||||
zuptsdk_free(archive);
|
||||
zuptsdk_secure_buf_destroy(pw);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
return die("extract_buffer", rc);
|
||||
}
|
||||
|
||||
/* 6. Verify */
|
||||
int ok = (extracted_sz == strlen(plaintext)) &&
|
||||
(memcmp(extracted, plaintext, extracted_sz) == 0);
|
||||
printf("Roundtrip: %s\n", ok ? "OK (byte-exact)" : "FAILED");
|
||||
printf("Extracted: %.*s", (int)extracted_sz, extracted);
|
||||
|
||||
/* 7. Clean up — always use zuptsdk_free for transferred memory */
|
||||
zuptsdk_free(extracted);
|
||||
zuptsdk_free(archive);
|
||||
zuptsdk_secure_buf_destroy(pw);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
605
sdk/include/zuptsdk.h
Normal file
605
sdk/include/zuptsdk.h
Normal file
|
|
@ -0,0 +1,605 @@
|
|||
/*
|
||||
* libzuptsdk — Public C ABI for the Zupt backup compression library
|
||||
*
|
||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*
|
||||
* Repository: https://git.securityops.co/cristiancmoises/zupt
|
||||
* Website: https://zupt.securityops.co
|
||||
* Contact: zupt@riseup.net
|
||||
*
|
||||
* --------------------------------------------------------------------------
|
||||
* STABILITY GUARANTEE
|
||||
* --------------------------------------------------------------------------
|
||||
* Every symbol declared in this header is part of the stable v1.0 ABI and
|
||||
* is gated behind the linker version tag ZUPTSDK_1.0. New symbols may be
|
||||
* added in minor versions (1.1, 1.2, ...) under new tags (ZUPTSDK_1.1, ...).
|
||||
* Existing symbols will never change signature within v1.x. Breaking
|
||||
* changes require a major version bump (libzuptsdk.so.2).
|
||||
*
|
||||
* No symbol prefixed with anything other than `zuptsdk_` or `ZUPTSDK_` is
|
||||
* part of this ABI. Do not link against internal `zupt_*` symbols even if
|
||||
* they appear in the static archive — they will disappear without notice.
|
||||
*
|
||||
* --------------------------------------------------------------------------
|
||||
* THREAD SAFETY
|
||||
* --------------------------------------------------------------------------
|
||||
* Every function that takes a `zuptsdk_ctx_t *` operates only on that
|
||||
* context's state and on caller-provided buffers. Concurrent calls on
|
||||
* DISTINCT contexts are safe (MT-Safe). Concurrent calls on the SAME
|
||||
* context are NOT safe (MT-Unsafe-Same-Context) unless explicitly
|
||||
* documented otherwise.
|
||||
*
|
||||
* --------------------------------------------------------------------------
|
||||
* MEMORY OWNERSHIP
|
||||
* --------------------------------------------------------------------------
|
||||
* Every function documents ownership using these conventions in the param
|
||||
* comments:
|
||||
* [in] caller owns, library reads only
|
||||
* [out] caller owns, library writes
|
||||
* [in,out] caller owns, library reads and writes
|
||||
* [transfers] ownership moves caller -> library (or library -> caller)
|
||||
* [borrowed] pointer valid only for the duration of the call
|
||||
*
|
||||
* Any function that returns a heap-allocated value via an output pointer
|
||||
* documents the corresponding zuptsdk_*_destroy() or zuptsdk_free() call
|
||||
* the caller must invoke. Calling free() on libc-allocated memory from a
|
||||
* different allocator is undefined; always use the documented destroyer.
|
||||
*
|
||||
* --------------------------------------------------------------------------
|
||||
* ERROR HANDLING
|
||||
* --------------------------------------------------------------------------
|
||||
* Functions return `int` where 0 == ZUPTSDK_OK and negative values are
|
||||
* `zuptsdk_error_t` codes. Use zuptsdk_strerror() for a static description
|
||||
* and zuptsdk_last_error_detail(ctx) for a thread-local detailed message
|
||||
* including filename, line number, and underlying errno where applicable.
|
||||
*
|
||||
* The library never calls abort(), exit(), or _exit(). It never writes to
|
||||
* stdout or stderr unless the caller explicitly enables logging via
|
||||
* zuptsdk_ctx_set_log_callback().
|
||||
*
|
||||
* --------------------------------------------------------------------------
|
||||
* SECURE MEMORY
|
||||
* --------------------------------------------------------------------------
|
||||
* Inputs and outputs containing secret material (passwords, raw keys,
|
||||
* decrypted plaintext keys) MUST be passed via `zuptsdk_secure_buffer_t`
|
||||
* to ensure mlock()-backed storage and explicit_bzero() on destroy.
|
||||
* Passing such material via plain `const uint8_t *` is allowed for
|
||||
* convenience but the library cannot guarantee zeroization of caller
|
||||
* memory in that case.
|
||||
*/
|
||||
|
||||
#ifndef ZUPTSDK_H
|
||||
#define ZUPTSDK_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* VERSION
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
#define ZUPTSDK_VERSION_MAJOR 1
|
||||
#define ZUPTSDK_VERSION_MINOR 0
|
||||
#define ZUPTSDK_VERSION_PATCH 0
|
||||
#define ZUPTSDK_VERSION_STRING "1.0.0"
|
||||
|
||||
/* Compile-time version check helper (negative if header older than required) */
|
||||
#define ZUPTSDK_VERSION_AT_LEAST(maj, min, pat) \
|
||||
((ZUPTSDK_VERSION_MAJOR > (maj)) || \
|
||||
(ZUPTSDK_VERSION_MAJOR == (maj) && ZUPTSDK_VERSION_MINOR > (min)) || \
|
||||
(ZUPTSDK_VERSION_MAJOR == (maj) && ZUPTSDK_VERSION_MINOR == (min) && \
|
||||
ZUPTSDK_VERSION_PATCH >= (pat)))
|
||||
|
||||
/**
|
||||
* Return the runtime version string of the linked library, e.g. "1.0.0".
|
||||
* The returned pointer is to static storage and must NOT be freed.
|
||||
*
|
||||
* Use this with the compile-time ZUPTSDK_VERSION_STRING to detect mismatch
|
||||
* between header and library at runtime.
|
||||
*/
|
||||
const char *zuptsdk_version_string(void);
|
||||
|
||||
/**
|
||||
* Verify that the linked library is at least the requested version.
|
||||
* Returns 0 if compatible, ZUPTSDK_ERR_VERSION_MISMATCH otherwise.
|
||||
* Call this once at startup before any other zuptsdk_* function.
|
||||
*/
|
||||
int zuptsdk_version_check(int major, int minor, int patch);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* ERRORS
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
typedef enum {
|
||||
ZUPTSDK_OK = 0,
|
||||
ZUPTSDK_ERR_INVALID_ARG = -1, /* NULL pointer, bad size, bad enum value */
|
||||
ZUPTSDK_ERR_NO_MEMORY = -2, /* malloc/calloc/realloc returned NULL */
|
||||
ZUPTSDK_ERR_IO = -3, /* read/write error; see errno detail */
|
||||
ZUPTSDK_ERR_BAD_ARCHIVE = -4, /* Magic mismatch or truncated header */
|
||||
ZUPTSDK_ERR_BAD_PASSWORD = -5, /* MAC verification failed */
|
||||
ZUPTSDK_ERR_BAD_KEY = -6, /* PQ key file malformed or wrong type */
|
||||
ZUPTSDK_ERR_BAD_MAC = -7, /* HMAC mismatch — archive corrupted or tampered */
|
||||
ZUPTSDK_ERR_BAD_VERSION = -8, /* Archive format version not supported */
|
||||
ZUPTSDK_ERR_BAD_CHECKSUM = -9, /* Block checksum mismatch */
|
||||
ZUPTSDK_ERR_BUFFER_TOO_SMALL = -10, /* Output buffer insufficient */
|
||||
ZUPTSDK_ERR_NOT_ENCRYPTED = -11, /* Tried to decrypt unencrypted archive */
|
||||
ZUPTSDK_ERR_PASSWORD_REQUIRED = -12, /* Archive needs password but none supplied */
|
||||
ZUPTSDK_ERR_PQ_KEY_REQUIRED = -13, /* Archive needs PQ key but none supplied */
|
||||
ZUPTSDK_ERR_UNSUPPORTED = -14, /* Feature not supported on this platform */
|
||||
ZUPTSDK_ERR_VERSION_MISMATCH = -15, /* Library older than requested */
|
||||
ZUPTSDK_ERR_PATH_TRAVERSAL = -16, /* "../" or absolute path in archive */
|
||||
ZUPTSDK_ERR_TOO_LARGE = -17, /* Decompressed size exceeds limit */
|
||||
ZUPTSDK_ERR_CRYPTO_FAIL = -18, /* Underlying crypto primitive failed */
|
||||
ZUPTSDK_ERR_CANCELLED = -19, /* Caller cancelled via progress callback */
|
||||
ZUPTSDK_ERR_INTERNAL = -99 /* Bug in library — please report */
|
||||
} zuptsdk_error_t;
|
||||
|
||||
/**
|
||||
* Static error description for a zuptsdk_error_t value.
|
||||
* Returned pointer is static and must not be freed. Always non-NULL.
|
||||
*/
|
||||
const char *zuptsdk_strerror(int err);
|
||||
|
||||
/**
|
||||
* Thread-local detailed error message from the most recent failed call.
|
||||
* The string includes file:line of the failure point and underlying errno
|
||||
* description where applicable. Returned pointer is to thread-local
|
||||
* storage, valid until the next failed zuptsdk_* call on this thread.
|
||||
* Returns "" if no error has been recorded on this thread.
|
||||
*/
|
||||
const char *zuptsdk_last_error_detail(void);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* OPAQUE TYPES (forward declarations only — no struct layout exposed)
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
typedef struct zuptsdk_ctx zuptsdk_ctx_t;
|
||||
typedef struct zuptsdk_options zuptsdk_options_t;
|
||||
typedef struct zuptsdk_archive_info zuptsdk_archive_info_t;
|
||||
typedef struct zuptsdk_secure_buf zuptsdk_secure_buf_t;
|
||||
typedef struct zuptsdk_keypair zuptsdk_keypair_t;
|
||||
typedef struct zuptsdk_pubkey zuptsdk_pubkey_t;
|
||||
typedef struct zuptsdk_privkey zuptsdk_privkey_t;
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* ENUMS
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
typedef enum {
|
||||
ZUPTSDK_CODEC_AUTO = 0, /* Hardware-adaptive (VaptVupt on AVX2, LZHP otherwise) */
|
||||
ZUPTSDK_CODEC_VAPTVUPT = 1, /* VaptVupt LZ + ANS entropy */
|
||||
ZUPTSDK_CODEC_LZHP = 2, /* LZ77 + Huffman + Byte Prediction */
|
||||
ZUPTSDK_CODEC_LZH = 3, /* LZ77 + Huffman */
|
||||
ZUPTSDK_CODEC_LZ = 4, /* LZ77 only */
|
||||
ZUPTSDK_CODEC_STORE = 5 /* No compression */
|
||||
} zuptsdk_codec_t;
|
||||
|
||||
typedef enum {
|
||||
ZUPTSDK_ENC_NONE = 0, /* No encryption */
|
||||
ZUPTSDK_ENC_PASSWORD = 1, /* PBKDF2 → AES-256-CTR + HMAC-SHA256 */
|
||||
ZUPTSDK_ENC_PQ_HYBRID = 2 /* ML-KEM-768 + X25519 hybrid KEM */
|
||||
} zuptsdk_encryption_t;
|
||||
|
||||
typedef enum {
|
||||
ZUPTSDK_LOG_ERROR = 0,
|
||||
ZUPTSDK_LOG_WARN = 1,
|
||||
ZUPTSDK_LOG_INFO = 2,
|
||||
ZUPTSDK_LOG_DEBUG = 3
|
||||
} zuptsdk_log_level_t;
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* CALLBACKS
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* Streaming read callback. Library calls this to obtain input bytes.
|
||||
* @param userdata [in] opaque pointer supplied at stream init
|
||||
* @param buf [out] destination buffer
|
||||
* @param max_bytes max bytes to read into buf
|
||||
* @return Number of bytes actually read (0 == EOF, < 0 == error).
|
||||
*/
|
||||
typedef int64_t (*zuptsdk_read_fn)(void *userdata, uint8_t *buf, size_t max_bytes);
|
||||
|
||||
/**
|
||||
* Streaming write callback. Library calls this to deliver output bytes.
|
||||
* @param userdata [in] opaque pointer supplied at stream init
|
||||
* @param buf [in] data to write
|
||||
* @param bytes number of bytes in buf
|
||||
* @return Number of bytes actually written (must equal `bytes` on success).
|
||||
*/
|
||||
typedef int64_t (*zuptsdk_write_fn)(void *userdata, const uint8_t *buf, size_t bytes);
|
||||
|
||||
/**
|
||||
* Progress callback. Library invokes periodically during long operations.
|
||||
* Return non-zero to cancel the operation; the in-flight call will then
|
||||
* return ZUPTSDK_ERR_CANCELLED.
|
||||
* @param userdata [in] opaque pointer set via zuptsdk_ctx_set_progress_callback
|
||||
* @param processed bytes processed so far
|
||||
* @param total total bytes (0 if unknown)
|
||||
* @return 0 to continue, non-zero to cancel.
|
||||
*/
|
||||
typedef int (*zuptsdk_progress_fn)(void *userdata, uint64_t processed, uint64_t total);
|
||||
|
||||
/**
|
||||
* Log callback. Receives diagnostic messages from the library.
|
||||
* Set via zuptsdk_ctx_set_log_callback(). NULL means no logging (default).
|
||||
* The string is null-terminated and valid only for the duration of the call.
|
||||
*/
|
||||
typedef void (*zuptsdk_log_fn)(void *userdata, zuptsdk_log_level_t level, const char *msg);
|
||||
|
||||
/**
|
||||
* Custom allocator hooks. Set globally via zuptsdk_set_allocator().
|
||||
* If any function is NULL, libc malloc/free/realloc is used.
|
||||
* realloc_fn must accept (NULL, n) as malloc(n) and (p, 0) as free(p).
|
||||
*/
|
||||
typedef struct {
|
||||
void *(*malloc_fn)(void *userdata, size_t size);
|
||||
void (*free_fn)(void *userdata, void *ptr);
|
||||
void *(*realloc_fn)(void *userdata, void *ptr, size_t size);
|
||||
void *userdata;
|
||||
} zuptsdk_allocator_t;
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* GLOBAL CONFIG
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* Install a custom allocator. Must be called before any other zuptsdk_*
|
||||
* function. Calling after contexts have been created is undefined.
|
||||
* Pass NULL to revert to libc allocator (only valid before first use).
|
||||
*
|
||||
* @param alloc [in,borrowed] allocator hooks; copied internally
|
||||
* @return ZUPTSDK_OK or ZUPTSDK_ERR_INVALID_ARG
|
||||
*/
|
||||
int zuptsdk_set_allocator(const zuptsdk_allocator_t *alloc);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* CONTEXT
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* Create a new SDK context. Each context holds its own thread pool,
|
||||
* progress callback, log callback, and error state. Contexts are
|
||||
* cheap to create — a few KB plus the configured thread count.
|
||||
*
|
||||
* @param ctx_out [out,transfers] pointer to receive new context
|
||||
* @return ZUPTSDK_OK on success, ZUPTSDK_ERR_NO_MEMORY on alloc failure.
|
||||
* On error, *ctx_out is set to NULL.
|
||||
*/
|
||||
int zuptsdk_ctx_create(zuptsdk_ctx_t **ctx_out);
|
||||
|
||||
/**
|
||||
* Destroy a context. Frees all owned resources including thread pool.
|
||||
* Safe to call with NULL. After this call, the pointer is invalid.
|
||||
*/
|
||||
void zuptsdk_ctx_destroy(zuptsdk_ctx_t *ctx);
|
||||
|
||||
/**
|
||||
* Set worker thread count. 0 == auto (one per CPU). Default is auto.
|
||||
* Returns ZUPTSDK_ERR_INVALID_ARG if ctx is NULL or threads > 256.
|
||||
*/
|
||||
int zuptsdk_ctx_set_threads(zuptsdk_ctx_t *ctx, int threads);
|
||||
|
||||
/**
|
||||
* Set progress callback for long-running operations on this context.
|
||||
* Pass NULL fn to clear. userdata is opaque to the library.
|
||||
*/
|
||||
int zuptsdk_ctx_set_progress_callback(zuptsdk_ctx_t *ctx,
|
||||
zuptsdk_progress_fn fn,
|
||||
void *userdata);
|
||||
|
||||
/**
|
||||
* Set log callback for diagnostic messages on this context.
|
||||
* Pass NULL fn to disable logging (default).
|
||||
*/
|
||||
int zuptsdk_ctx_set_log_callback(zuptsdk_ctx_t *ctx,
|
||||
zuptsdk_log_fn fn,
|
||||
zuptsdk_log_level_t min_level,
|
||||
void *userdata);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* OPTIONS
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* Create a default-initialized options bag for compress/encrypt operations.
|
||||
* Defaults: codec=AUTO, level=7, no encryption, no dedup, no solid mode.
|
||||
*/
|
||||
int zuptsdk_options_create(zuptsdk_options_t **opts_out);
|
||||
void zuptsdk_options_destroy(zuptsdk_options_t *opts);
|
||||
|
||||
int zuptsdk_options_set_codec(zuptsdk_options_t *opts, zuptsdk_codec_t codec);
|
||||
int zuptsdk_options_set_level(zuptsdk_options_t *opts, int level /* 1..9 */);
|
||||
int zuptsdk_options_set_dedup(zuptsdk_options_t *opts, int enabled);
|
||||
int zuptsdk_options_set_solid(zuptsdk_options_t *opts, int enabled);
|
||||
int zuptsdk_options_set_block_size(zuptsdk_options_t *opts, size_t bytes);
|
||||
|
||||
/**
|
||||
* Maximum decompressed output size. Decompression aborts with
|
||||
* ZUPTSDK_ERR_TOO_LARGE if exceeded. 0 == unlimited (NOT recommended
|
||||
* for untrusted input — zip-bomb attack vector). Default: 16 GiB.
|
||||
*/
|
||||
int zuptsdk_options_set_max_decompressed(zuptsdk_options_t *opts,
|
||||
uint64_t max_bytes);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* SECURE BUFFERS (for passwords and key material)
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* Allocate a secure buffer: backing memory is mlock()ed (locked into RAM,
|
||||
* never swapped to disk) and explicit_bzero()ed on destroy.
|
||||
*
|
||||
* @param size requested size in bytes (1..65536)
|
||||
* @param buf_out [out,transfers] receives buffer handle
|
||||
* @return ZUPTSDK_OK on success.
|
||||
*/
|
||||
int zuptsdk_secure_buf_create(size_t size, zuptsdk_secure_buf_t **buf_out);
|
||||
|
||||
/**
|
||||
* Destroy a secure buffer. Memory is zeroed and unlocked before free.
|
||||
* Safe to call with NULL.
|
||||
*/
|
||||
void zuptsdk_secure_buf_destroy(zuptsdk_secure_buf_t *buf);
|
||||
|
||||
/**
|
||||
* Get raw pointer to the secure buffer's storage. Pointer is valid until
|
||||
* zuptsdk_secure_buf_destroy() is called. Caller may read or write up to
|
||||
* the buffer's size.
|
||||
*
|
||||
* @param buf [in]
|
||||
* @param data_out [out,borrowed] receives pointer to storage
|
||||
* @param size_out [out] receives buffer size
|
||||
*/
|
||||
int zuptsdk_secure_buf_get(zuptsdk_secure_buf_t *buf,
|
||||
uint8_t **data_out, size_t *size_out);
|
||||
|
||||
/**
|
||||
* Convenience: copy data into a new secure buffer.
|
||||
* Useful when migrating an existing plain buffer to secure storage.
|
||||
*/
|
||||
int zuptsdk_secure_buf_from_data(const uint8_t *data, size_t size,
|
||||
zuptsdk_secure_buf_t **buf_out);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* KEYS (PQ hybrid: ML-KEM-768 + X25519)
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* Generate a fresh hybrid keypair. Uses the system CSPRNG.
|
||||
*
|
||||
* @param ctx [in]
|
||||
* @param kp_out [out,transfers] receives new keypair
|
||||
* @return ZUPTSDK_OK on success, ZUPTSDK_ERR_CRYPTO_FAIL on RNG failure.
|
||||
*/
|
||||
int zuptsdk_keypair_generate(zuptsdk_ctx_t *ctx, zuptsdk_keypair_t **kp_out);
|
||||
|
||||
void zuptsdk_keypair_destroy(zuptsdk_keypair_t *kp);
|
||||
|
||||
/**
|
||||
* Save private key to a file. The file is written with mode 0600 on POSIX.
|
||||
* Recommended extension: ".key".
|
||||
*/
|
||||
int zuptsdk_keypair_save_private(const zuptsdk_keypair_t *kp, const char *path);
|
||||
|
||||
/**
|
||||
* Save public key to a file. World-readable.
|
||||
* Recommended extension: ".pub" or "_public.key".
|
||||
*/
|
||||
int zuptsdk_keypair_save_public(const zuptsdk_keypair_t *kp, const char *path);
|
||||
|
||||
/**
|
||||
* Load a private key from a file.
|
||||
* @param path [in]
|
||||
* @param key_out [out,transfers]
|
||||
*/
|
||||
int zuptsdk_privkey_load(const char *path, zuptsdk_privkey_t **key_out);
|
||||
void zuptsdk_privkey_destroy(zuptsdk_privkey_t *key);
|
||||
|
||||
/**
|
||||
* Load a public key from a file.
|
||||
*/
|
||||
int zuptsdk_pubkey_load(const char *path, zuptsdk_pubkey_t **key_out);
|
||||
void zuptsdk_pubkey_destroy(zuptsdk_pubkey_t *key);
|
||||
|
||||
/**
|
||||
* Derive public key from private key (no I/O).
|
||||
*/
|
||||
int zuptsdk_privkey_get_public(const zuptsdk_privkey_t *priv,
|
||||
zuptsdk_pubkey_t **pub_out);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* COMPRESS / DECOMPRESS — buffer mode (for small archives)
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* Compress an in-memory file list into a single archive buffer.
|
||||
*
|
||||
* @param ctx [in]
|
||||
* @param opts [in,borrowed] compression and encryption options
|
||||
* @param file_paths [in] array of filesystem paths to add
|
||||
* @param file_count number of paths in file_paths
|
||||
* @param password [in,nullable] password as a secure buffer; NULL for no pw
|
||||
* @param recipient_pk [in,nullable] PQ public key for encryption; NULL for no PQ
|
||||
* @param archive_out [out,transfers] receives malloc'd archive bytes;
|
||||
* caller must free with zuptsdk_free()
|
||||
* @param archive_sz [out] size of returned archive
|
||||
* @return ZUPTSDK_OK on success.
|
||||
*/
|
||||
int zuptsdk_compress_files(zuptsdk_ctx_t *ctx,
|
||||
const zuptsdk_options_t *opts,
|
||||
const char *const *file_paths,
|
||||
size_t file_count,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_pubkey_t *recipient_pk,
|
||||
uint8_t **archive_out,
|
||||
size_t *archive_sz);
|
||||
|
||||
/**
|
||||
* Compress a single in-memory data buffer. Useful for SDK consumers that
|
||||
* have data in memory and want a self-contained archive.
|
||||
*
|
||||
* @param logical_name [in] name to record inside the archive (e.g. "data.bin")
|
||||
*/
|
||||
int zuptsdk_compress_buffer(zuptsdk_ctx_t *ctx,
|
||||
const zuptsdk_options_t *opts,
|
||||
const char *logical_name,
|
||||
const uint8_t *data, size_t data_sz,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_pubkey_t *recipient_pk,
|
||||
uint8_t **archive_out,
|
||||
size_t *archive_sz);
|
||||
|
||||
/**
|
||||
* Extract an archive into a directory.
|
||||
*
|
||||
* @param dest_dir [in] target directory; created if missing
|
||||
* @param password [in,nullable]
|
||||
* @param recipient_sk [in,nullable] PQ private key
|
||||
*/
|
||||
int zuptsdk_extract_to_dir(zuptsdk_ctx_t *ctx,
|
||||
const uint8_t *archive, size_t archive_sz,
|
||||
const char *dest_dir,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_privkey_t *recipient_sk);
|
||||
|
||||
/**
|
||||
* Extract a single-file archive (one created with zuptsdk_compress_buffer)
|
||||
* back into a memory buffer.
|
||||
*
|
||||
* @param data_out [out,transfers] caller frees with zuptsdk_free()
|
||||
* @param data_sz [out]
|
||||
*/
|
||||
int zuptsdk_extract_buffer(zuptsdk_ctx_t *ctx,
|
||||
const uint8_t *archive, size_t archive_sz,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_privkey_t *recipient_sk,
|
||||
uint8_t **data_out, size_t *data_sz);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* COMPRESS / DECOMPRESS — streaming mode (for large archives)
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* Compress from a read callback to a write callback. Streaming version
|
||||
* with no archive size limit — suitable for piping to network sockets,
|
||||
* encrypted volumes, or any backend with a write_fn.
|
||||
*
|
||||
* @param input [in] read callback supplying source bytes
|
||||
* @param input_ud [in] userdata passed to read callback
|
||||
* @param input_name [in] logical filename to record in archive
|
||||
* @param input_total total bytes to read; 0 if unknown
|
||||
* @param output [in] write callback receiving archive bytes
|
||||
* @param output_ud [in] userdata passed to write callback
|
||||
*/
|
||||
int zuptsdk_compress_stream(zuptsdk_ctx_t *ctx,
|
||||
const zuptsdk_options_t *opts,
|
||||
zuptsdk_read_fn input, void *input_ud,
|
||||
const char *input_name, uint64_t input_total,
|
||||
zuptsdk_write_fn output, void *output_ud,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_pubkey_t *recipient_pk);
|
||||
|
||||
/**
|
||||
* Decompress an archive read from a callback, writing extracted single-file
|
||||
* content to a write callback.
|
||||
*/
|
||||
int zuptsdk_decompress_stream(zuptsdk_ctx_t *ctx,
|
||||
zuptsdk_read_fn input, void *input_ud,
|
||||
zuptsdk_write_fn output, void *output_ud,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_privkey_t *recipient_sk);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* VERIFY / INFO
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* Verify all block checksums and (if encrypted) HMAC of an archive.
|
||||
* No data is written to disk. Returns ZUPTSDK_OK if every block validates.
|
||||
*/
|
||||
int zuptsdk_verify(zuptsdk_ctx_t *ctx,
|
||||
const uint8_t *archive, size_t archive_sz,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_privkey_t *recipient_sk);
|
||||
|
||||
/**
|
||||
* Read archive metadata without password or key. Returns header info only;
|
||||
* does not decrypt block contents.
|
||||
*
|
||||
* @param info_out [out,transfers] receives info object;
|
||||
* caller must zuptsdk_archive_info_destroy()
|
||||
*/
|
||||
int zuptsdk_archive_info_read(zuptsdk_ctx_t *ctx,
|
||||
const uint8_t *archive, size_t archive_sz,
|
||||
zuptsdk_archive_info_t **info_out);
|
||||
|
||||
void zuptsdk_archive_info_destroy(zuptsdk_archive_info_t *info);
|
||||
|
||||
/* Getters — opaque struct, all fields accessed via these functions. */
|
||||
int zuptsdk_archive_info_format_major(const zuptsdk_archive_info_t *info);
|
||||
int zuptsdk_archive_info_format_minor(const zuptsdk_archive_info_t *info);
|
||||
const char *zuptsdk_archive_info_uuid(const zuptsdk_archive_info_t *info);
|
||||
int64_t zuptsdk_archive_info_created_unix(const zuptsdk_archive_info_t *info);
|
||||
uint64_t zuptsdk_archive_info_size(const zuptsdk_archive_info_t *info);
|
||||
uint32_t zuptsdk_archive_info_block_count(const zuptsdk_archive_info_t *info);
|
||||
int zuptsdk_archive_info_is_encrypted(const zuptsdk_archive_info_t *info);
|
||||
int zuptsdk_archive_info_is_pq_hybrid(const zuptsdk_archive_info_t *info);
|
||||
int zuptsdk_archive_info_is_solid(const zuptsdk_archive_info_t *info);
|
||||
int zuptsdk_archive_info_is_dedup(const zuptsdk_archive_info_t *info);
|
||||
int zuptsdk_archive_info_is_disk_image(const zuptsdk_archive_info_t *info);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* DISK BACKUP / RESTORE
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* Backup a block device or disk image file to an archive.
|
||||
* REQUIRES root/admin privileges to read raw block devices on most OSes.
|
||||
*/
|
||||
int zuptsdk_disk_backup(zuptsdk_ctx_t *ctx,
|
||||
const zuptsdk_options_t *opts,
|
||||
const char *source_device_or_image,
|
||||
const char *output_archive_path,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_pubkey_t *recipient_pk);
|
||||
|
||||
/**
|
||||
* Restore a disk backup archive to a block device or image file.
|
||||
* DESTRUCTIVE: target is overwritten without confirmation.
|
||||
*/
|
||||
int zuptsdk_disk_restore(zuptsdk_ctx_t *ctx,
|
||||
const char *archive_path,
|
||||
const char *target_device_or_image,
|
||||
zuptsdk_secure_buf_t *password,
|
||||
const zuptsdk_privkey_t *recipient_sk);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* MISC
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* Free memory returned by the library via [transfers] output pointers.
|
||||
* Safe to call with NULL.
|
||||
*
|
||||
* Always use this — never free() — for SDK-allocated memory, since the
|
||||
* library may have been built with a custom allocator.
|
||||
*/
|
||||
void zuptsdk_free(void *ptr);
|
||||
|
||||
/**
|
||||
* Best-effort secure zero of a buffer. Resistant to dead-store elimination
|
||||
* by the optimizer. Use for caller-managed sensitive memory.
|
||||
*/
|
||||
void zuptsdk_secure_zero(void *buf, size_t bytes);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* ZUPTSDK_H */
|
||||
1149
sdk/src/zuptsdk.c
Normal file
1149
sdk/src/zuptsdk.c
Normal file
File diff suppressed because it is too large
Load diff
122
sdk/tests/test_python.py
Normal file
122
sdk/tests/test_python.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
zuptsdk Python binding test
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
# Add path so we can import without installing
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
os.environ.setdefault("ZUPTSDK_LIBRARY",
|
||||
os.path.join(os.path.dirname(__file__), "..", "..", "build", "libzuptsdk.so.1"))
|
||||
|
||||
import zuptsdk
|
||||
|
||||
PASS = 0
|
||||
FAIL = 0
|
||||
|
||||
def test(name, fn):
|
||||
global PASS, FAIL
|
||||
try:
|
||||
fn()
|
||||
print(f" {name:<60} PASS")
|
||||
PASS += 1
|
||||
except Exception as e:
|
||||
print(f" {name:<60} FAIL: {e!r}")
|
||||
FAIL += 1
|
||||
|
||||
|
||||
print(f"\n zuptsdk Python bindings — version {zuptsdk.__version__}\n")
|
||||
|
||||
DATA = b"Hello world from Python via cffi!\n" * 10
|
||||
|
||||
|
||||
def t_version():
|
||||
assert zuptsdk.__version__ == "1.0.0", f"got {zuptsdk.__version__}"
|
||||
|
||||
|
||||
def t_roundtrip_plain():
|
||||
with zuptsdk.Context() as ctx:
|
||||
arc = ctx.compress_buffer(DATA, name="hello.txt")
|
||||
assert isinstance(arc, bytes)
|
||||
assert len(arc) > 0
|
||||
out = ctx.extract_buffer(arc)
|
||||
assert out == DATA
|
||||
|
||||
|
||||
def t_roundtrip_password():
|
||||
with zuptsdk.Context() as ctx:
|
||||
arc = ctx.compress_buffer(DATA, name="secret.txt",
|
||||
password=b"correct horse battery staple")
|
||||
out = ctx.extract_buffer(arc, password=b"correct horse battery staple")
|
||||
assert out == DATA
|
||||
|
||||
|
||||
def t_wrong_password():
|
||||
with zuptsdk.Context() as ctx:
|
||||
arc = ctx.compress_buffer(DATA, name="x.txt", password=b"good")
|
||||
try:
|
||||
ctx.extract_buffer(arc, password=b"bad")
|
||||
except zuptsdk.ZuptError:
|
||||
return
|
||||
raise AssertionError("wrong password was accepted")
|
||||
|
||||
|
||||
def t_secure_buf_password():
|
||||
with zuptsdk.Context() as ctx, zuptsdk.SecureBuf(b"locked-pw") as pw:
|
||||
arc = ctx.compress_buffer(DATA, name="x.txt", password=pw)
|
||||
out = ctx.extract_buffer(arc, password=pw)
|
||||
assert out == DATA
|
||||
|
||||
|
||||
def t_pq_keypair():
|
||||
with zuptsdk.Context() as ctx:
|
||||
kp = ctx.generate_keypair()
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
kp.save(os.path.join(td, "mykey"))
|
||||
arc = ctx.compress_buffer(DATA, name="pq.txt", public_key=kp.public)
|
||||
out = ctx.extract_buffer(arc, private_key=kp.private)
|
||||
assert out == DATA
|
||||
|
||||
|
||||
def t_verify_and_info():
|
||||
with zuptsdk.Context() as ctx:
|
||||
arc = ctx.compress_buffer(DATA, name="v.txt")
|
||||
assert ctx.verify(arc) is True
|
||||
info = ctx.info(arc)
|
||||
assert info.format_major >= 1
|
||||
assert info.is_encrypted is False
|
||||
assert len(info.uuid) == 36, info.uuid
|
||||
|
||||
|
||||
def t_corrupted_archive():
|
||||
with zuptsdk.Context() as ctx:
|
||||
# Use enough data that flipping a byte mid-stream lands in the
|
||||
# compressed payload, not in a zero-padded region.
|
||||
big = DATA * 100
|
||||
arc = bytearray(ctx.compress_buffer(big, name="c.txt"))
|
||||
# Flip a byte two-thirds of the way through — well into compressed
|
||||
# data, well before the index/footer.
|
||||
if len(arc) > 200:
|
||||
offset = (len(arc) * 2) // 3
|
||||
arc[offset] ^= 0xFF
|
||||
try:
|
||||
ctx.verify(bytes(arc))
|
||||
except zuptsdk.ZuptError:
|
||||
return
|
||||
raise AssertionError("corrupted archive accepted")
|
||||
|
||||
|
||||
test("version constant", t_version)
|
||||
test("roundtrip plain", t_roundtrip_plain)
|
||||
test("roundtrip password (bytes)", t_roundtrip_password)
|
||||
test("wrong password rejected", t_wrong_password)
|
||||
test("roundtrip with SecureBuf", t_secure_buf_password)
|
||||
test("PQ keypair generate + roundtrip", t_pq_keypair)
|
||||
test("verify and info", t_verify_and_info)
|
||||
test("corrupted archive rejected", t_corrupted_archive)
|
||||
|
||||
print(f"\n Results: {PASS} passed, {FAIL} failed\n")
|
||||
sys.exit(0 if FAIL == 0 else 1)
|
||||
412
sdk/tests/test_sdk_roundtrip.c
Normal file
412
sdk/tests/test_sdk_roundtrip.c
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
* libzuptsdk roundtrip test
|
||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*
|
||||
* Exercises every public SDK function with a byte-exact verification.
|
||||
* Returns 0 on success; non-zero on any failure.
|
||||
*/
|
||||
|
||||
#define _DEFAULT_SOURCE 1
|
||||
#include <zuptsdk.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
|
||||
#define TEST(name) do { \
|
||||
fprintf(stderr, " %-60s", name); \
|
||||
fflush(stderr); \
|
||||
} while (0)
|
||||
|
||||
#define PASS() do { \
|
||||
fprintf(stderr, "PASS\n"); \
|
||||
g_pass++; \
|
||||
} while (0)
|
||||
|
||||
#define FAIL(reason) do { \
|
||||
fprintf(stderr, "FAIL: %s\n", reason); \
|
||||
if (zuptsdk_last_error_detail()[0]) \
|
||||
fprintf(stderr, " detail: %s\n", zuptsdk_last_error_detail()); \
|
||||
g_fail++; \
|
||||
} while (0)
|
||||
|
||||
#define CHECK(rc, msg) do { \
|
||||
if ((rc) != ZUPTSDK_OK) { FAIL(msg); return; } \
|
||||
} while (0)
|
||||
|
||||
static const uint8_t TEST_DATA[] =
|
||||
"Post-quantum backup test data for the libzuptsdk roundtrip suite. "
|
||||
"This payload is repeated to ensure compression actually does something. "
|
||||
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
|
||||
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
|
||||
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
|
||||
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
|
||||
"Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod. "
|
||||
"End of test data.\n";
|
||||
|
||||
static void test_version(void) {
|
||||
TEST("version_string returns non-NULL");
|
||||
const char *v = zuptsdk_version_string();
|
||||
if (!v || strlen(v) == 0) { FAIL("empty"); return; }
|
||||
if (strcmp(v, ZUPTSDK_VERSION_STRING) != 0) { FAIL("mismatch"); return; }
|
||||
PASS();
|
||||
|
||||
TEST("version_check accepts current version");
|
||||
int rc = zuptsdk_version_check(ZUPTSDK_VERSION_MAJOR,
|
||||
ZUPTSDK_VERSION_MINOR,
|
||||
ZUPTSDK_VERSION_PATCH);
|
||||
CHECK(rc, "rejected own version");
|
||||
PASS();
|
||||
|
||||
TEST("version_check rejects future version");
|
||||
rc = zuptsdk_version_check(99, 99, 99);
|
||||
if (rc != ZUPTSDK_ERR_VERSION_MISMATCH) { FAIL("should reject"); return; }
|
||||
PASS();
|
||||
}
|
||||
|
||||
static void test_strerror(void) {
|
||||
TEST("strerror handles every error code");
|
||||
for (int e = 0; e >= -100; e--) {
|
||||
const char *s = zuptsdk_strerror(e);
|
||||
if (!s || !*s) { FAIL("empty"); return; }
|
||||
}
|
||||
PASS();
|
||||
}
|
||||
|
||||
static void test_context(void) {
|
||||
TEST("ctx_create / ctx_destroy");
|
||||
zuptsdk_ctx_t *c = NULL;
|
||||
CHECK(zuptsdk_ctx_create(&c), "create failed");
|
||||
if (!c) { FAIL("ctx is NULL"); return; }
|
||||
zuptsdk_ctx_destroy(c);
|
||||
zuptsdk_ctx_destroy(NULL); /* should not crash */
|
||||
PASS();
|
||||
|
||||
TEST("ctx_set_threads validates range");
|
||||
CHECK(zuptsdk_ctx_create(&c), "create");
|
||||
int rc = zuptsdk_ctx_set_threads(c, 0); /* auto */
|
||||
CHECK(rc, "auto");
|
||||
rc = zuptsdk_ctx_set_threads(c, 4); /* normal */
|
||||
CHECK(rc, "4");
|
||||
rc = zuptsdk_ctx_set_threads(c, -1); /* invalid */
|
||||
if (rc != ZUPTSDK_ERR_INVALID_ARG) { FAIL("should reject -1"); zuptsdk_ctx_destroy(c); return; }
|
||||
rc = zuptsdk_ctx_set_threads(c, 999); /* invalid */
|
||||
if (rc != ZUPTSDK_ERR_INVALID_ARG) { FAIL("should reject 999"); zuptsdk_ctx_destroy(c); return; }
|
||||
zuptsdk_ctx_destroy(c);
|
||||
PASS();
|
||||
}
|
||||
|
||||
static void test_options(void) {
|
||||
TEST("options builder full sequence");
|
||||
zuptsdk_options_t *o = NULL;
|
||||
CHECK(zuptsdk_options_create(&o), "create");
|
||||
CHECK(zuptsdk_options_set_codec(o, ZUPTSDK_CODEC_AUTO), "codec auto");
|
||||
CHECK(zuptsdk_options_set_codec(o, ZUPTSDK_CODEC_VAPTVUPT), "codec vv");
|
||||
CHECK(zuptsdk_options_set_level(o, 7), "level 7");
|
||||
CHECK(zuptsdk_options_set_dedup(o, 1), "dedup");
|
||||
CHECK(zuptsdk_options_set_solid(o, 0), "solid");
|
||||
CHECK(zuptsdk_options_set_max_decompressed(o, 1024 * 1024), "max");
|
||||
int rc = zuptsdk_options_set_level(o, 99);
|
||||
if (rc != ZUPTSDK_ERR_INVALID_ARG) { FAIL("level 99 should fail"); zuptsdk_options_destroy(o); return; }
|
||||
zuptsdk_options_destroy(o);
|
||||
PASS();
|
||||
}
|
||||
|
||||
static void test_secure_buf(void) {
|
||||
TEST("secure_buf create/get/destroy");
|
||||
zuptsdk_secure_buf_t *b = NULL;
|
||||
CHECK(zuptsdk_secure_buf_create(64, &b), "create");
|
||||
uint8_t *data = NULL; size_t sz = 0;
|
||||
CHECK(zuptsdk_secure_buf_get(b, &data, &sz), "get");
|
||||
if (sz != 64) { FAIL("size wrong"); zuptsdk_secure_buf_destroy(b); return; }
|
||||
memset(data, 0xAA, 64);
|
||||
if (data[0] != 0xAA || data[63] != 0xAA) { FAIL("write/read"); zuptsdk_secure_buf_destroy(b); return; }
|
||||
zuptsdk_secure_buf_destroy(b);
|
||||
PASS();
|
||||
|
||||
TEST("secure_buf_from_data copies");
|
||||
const uint8_t src[] = "secret password value!";
|
||||
CHECK(zuptsdk_secure_buf_from_data(src, sizeof(src) - 1, &b), "from_data");
|
||||
CHECK(zuptsdk_secure_buf_get(b, &data, &sz), "get");
|
||||
if (sz != sizeof(src) - 1) { FAIL("size"); zuptsdk_secure_buf_destroy(b); return; }
|
||||
if (memcmp(data, src, sz) != 0) { FAIL("content"); zuptsdk_secure_buf_destroy(b); return; }
|
||||
zuptsdk_secure_buf_destroy(b);
|
||||
PASS();
|
||||
}
|
||||
|
||||
static int byteexact(const uint8_t *a, size_t na, const uint8_t *b, size_t nb) {
|
||||
return (na == nb) && (memcmp(a, b, na) == 0);
|
||||
}
|
||||
|
||||
static void test_compress_buffer_plain(void) {
|
||||
TEST("compress_buffer + extract_buffer (plain)");
|
||||
zuptsdk_ctx_t *ctx = NULL;
|
||||
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
|
||||
|
||||
zuptsdk_options_t *opts = NULL;
|
||||
CHECK(zuptsdk_options_create(&opts), "opts");
|
||||
zuptsdk_options_set_codec(opts, ZUPTSDK_CODEC_AUTO);
|
||||
zuptsdk_options_set_level(opts, 5);
|
||||
|
||||
uint8_t *arc = NULL; size_t arc_sz = 0;
|
||||
int rc = zuptsdk_compress_buffer(ctx, opts, "test.txt",
|
||||
TEST_DATA, sizeof(TEST_DATA) - 1,
|
||||
NULL, NULL, &arc, &arc_sz);
|
||||
if (rc != ZUPTSDK_OK) { FAIL("compress"); zuptsdk_options_destroy(opts); zuptsdk_ctx_destroy(ctx); return; }
|
||||
|
||||
uint8_t *out = NULL; size_t out_sz = 0;
|
||||
rc = zuptsdk_extract_buffer(ctx, arc, arc_sz, NULL, NULL, &out, &out_sz);
|
||||
zuptsdk_free(arc);
|
||||
|
||||
if (rc != ZUPTSDK_OK) { FAIL("extract"); zuptsdk_options_destroy(opts); zuptsdk_ctx_destroy(ctx); return; }
|
||||
if (!byteexact(out, out_sz, TEST_DATA, sizeof(TEST_DATA) - 1)) {
|
||||
FAIL("byte mismatch");
|
||||
zuptsdk_free(out);
|
||||
zuptsdk_options_destroy(opts);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
return;
|
||||
}
|
||||
zuptsdk_free(out);
|
||||
zuptsdk_options_destroy(opts);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
PASS();
|
||||
}
|
||||
|
||||
static void test_compress_buffer_password(void) {
|
||||
TEST("compress_buffer + extract_buffer (password)");
|
||||
zuptsdk_ctx_t *ctx = NULL;
|
||||
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
|
||||
|
||||
zuptsdk_options_t *opts = NULL;
|
||||
CHECK(zuptsdk_options_create(&opts), "opts");
|
||||
|
||||
zuptsdk_secure_buf_t *pw = NULL;
|
||||
const uint8_t pwbuf[] = "Tr0ub4dor&3";
|
||||
CHECK(zuptsdk_secure_buf_from_data(pwbuf, sizeof(pwbuf) - 1, &pw), "pw create");
|
||||
|
||||
uint8_t *arc = NULL; size_t arc_sz = 0;
|
||||
int rc = zuptsdk_compress_buffer(ctx, opts, "secret.txt",
|
||||
TEST_DATA, sizeof(TEST_DATA) - 1,
|
||||
pw, NULL, &arc, &arc_sz);
|
||||
if (rc != ZUPTSDK_OK) {
|
||||
FAIL("compress");
|
||||
zuptsdk_secure_buf_destroy(pw);
|
||||
zuptsdk_options_destroy(opts);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *out = NULL; size_t out_sz = 0;
|
||||
rc = zuptsdk_extract_buffer(ctx, arc, arc_sz, pw, NULL, &out, &out_sz);
|
||||
zuptsdk_free(arc);
|
||||
|
||||
int ok = (rc == ZUPTSDK_OK) && byteexact(out, out_sz, TEST_DATA, sizeof(TEST_DATA) - 1);
|
||||
zuptsdk_free(out);
|
||||
zuptsdk_secure_buf_destroy(pw);
|
||||
zuptsdk_options_destroy(opts);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
if (!ok) { FAIL("byte mismatch or rc != OK"); return; }
|
||||
PASS();
|
||||
}
|
||||
|
||||
static void test_compress_buffer_wrong_password(void) {
|
||||
TEST("extract_buffer rejects wrong password");
|
||||
zuptsdk_ctx_t *ctx = NULL;
|
||||
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
|
||||
|
||||
zuptsdk_options_t *opts = NULL;
|
||||
CHECK(zuptsdk_options_create(&opts), "opts");
|
||||
|
||||
zuptsdk_secure_buf_t *pw_good = NULL, *pw_bad = NULL;
|
||||
zuptsdk_secure_buf_from_data((const uint8_t*)"correct", 7, &pw_good);
|
||||
zuptsdk_secure_buf_from_data((const uint8_t*)"WRONG-pw", 8, &pw_bad);
|
||||
|
||||
uint8_t *arc = NULL; size_t arc_sz = 0;
|
||||
int rc = zuptsdk_compress_buffer(ctx, opts, "x.txt",
|
||||
TEST_DATA, sizeof(TEST_DATA) - 1,
|
||||
pw_good, NULL, &arc, &arc_sz);
|
||||
if (rc != ZUPTSDK_OK) { FAIL("compress"); goto cleanup; }
|
||||
|
||||
uint8_t *out = NULL; size_t out_sz = 0;
|
||||
rc = zuptsdk_extract_buffer(ctx, arc, arc_sz, pw_bad, NULL, &out, &out_sz);
|
||||
if (rc == ZUPTSDK_OK) {
|
||||
FAIL("wrong password accepted");
|
||||
zuptsdk_free(out);
|
||||
zuptsdk_free(arc);
|
||||
goto cleanup;
|
||||
}
|
||||
zuptsdk_free(arc);
|
||||
PASS();
|
||||
|
||||
cleanup:
|
||||
zuptsdk_secure_buf_destroy(pw_good);
|
||||
zuptsdk_secure_buf_destroy(pw_bad);
|
||||
zuptsdk_options_destroy(opts);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
}
|
||||
|
||||
static void test_keypair_pq(void) {
|
||||
TEST("keypair_generate + compress_pq + extract_pq");
|
||||
zuptsdk_ctx_t *ctx = NULL;
|
||||
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
|
||||
|
||||
zuptsdk_keypair_t *kp = NULL;
|
||||
int rc = zuptsdk_keypair_generate(ctx, &kp);
|
||||
if (rc != ZUPTSDK_OK) { FAIL("keygen"); zuptsdk_ctx_destroy(ctx); return; }
|
||||
|
||||
/* Save and load to exercise that path too */
|
||||
rc = zuptsdk_keypair_save_private(kp, "/tmp/_zsdk_priv.key");
|
||||
if (rc != ZUPTSDK_OK) { FAIL("save priv"); goto err; }
|
||||
rc = zuptsdk_keypair_save_public(kp, "/tmp/_zsdk_pub.key");
|
||||
if (rc != ZUPTSDK_OK) { FAIL("save pub"); goto err; }
|
||||
|
||||
zuptsdk_pubkey_t *pub = NULL;
|
||||
zuptsdk_privkey_t *priv = NULL;
|
||||
rc = zuptsdk_pubkey_load("/tmp/_zsdk_pub.key", &pub);
|
||||
if (rc != ZUPTSDK_OK) { FAIL("load pub"); goto err; }
|
||||
rc = zuptsdk_privkey_load("/tmp/_zsdk_priv.key", &priv);
|
||||
if (rc != ZUPTSDK_OK) { FAIL("load priv"); zuptsdk_pubkey_destroy(pub); goto err; }
|
||||
|
||||
zuptsdk_options_t *opts = NULL;
|
||||
zuptsdk_options_create(&opts);
|
||||
|
||||
uint8_t *arc = NULL; size_t arc_sz = 0;
|
||||
rc = zuptsdk_compress_buffer(ctx, opts, "pq.txt",
|
||||
TEST_DATA, sizeof(TEST_DATA) - 1,
|
||||
NULL, pub, &arc, &arc_sz);
|
||||
if (rc != ZUPTSDK_OK) {
|
||||
FAIL("compress_pq");
|
||||
zuptsdk_pubkey_destroy(pub);
|
||||
zuptsdk_privkey_destroy(priv);
|
||||
zuptsdk_options_destroy(opts);
|
||||
goto err;
|
||||
}
|
||||
|
||||
uint8_t *out = NULL; size_t out_sz = 0;
|
||||
rc = zuptsdk_extract_buffer(ctx, arc, arc_sz, NULL, priv, &out, &out_sz);
|
||||
int ok = (rc == ZUPTSDK_OK) && byteexact(out, out_sz, TEST_DATA, sizeof(TEST_DATA) - 1);
|
||||
|
||||
zuptsdk_free(arc);
|
||||
zuptsdk_free(out);
|
||||
zuptsdk_pubkey_destroy(pub);
|
||||
zuptsdk_privkey_destroy(priv);
|
||||
zuptsdk_options_destroy(opts);
|
||||
|
||||
unlink("/tmp/_zsdk_priv.key");
|
||||
unlink("/tmp/_zsdk_pub.key");
|
||||
|
||||
if (!ok) { FAIL("byte mismatch or rc != OK"); zuptsdk_keypair_destroy(kp); zuptsdk_ctx_destroy(ctx); return; }
|
||||
zuptsdk_keypair_destroy(kp);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
PASS();
|
||||
return;
|
||||
|
||||
err:
|
||||
zuptsdk_keypair_destroy(kp);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
}
|
||||
|
||||
static void test_verify_and_info(void) {
|
||||
TEST("verify and archive_info_read");
|
||||
zuptsdk_ctx_t *ctx = NULL;
|
||||
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
|
||||
zuptsdk_options_t *opts = NULL;
|
||||
zuptsdk_options_create(&opts);
|
||||
|
||||
uint8_t *arc = NULL; size_t arc_sz = 0;
|
||||
int rc = zuptsdk_compress_buffer(ctx, opts, "v.txt",
|
||||
TEST_DATA, sizeof(TEST_DATA) - 1,
|
||||
NULL, NULL, &arc, &arc_sz);
|
||||
if (rc != ZUPTSDK_OK) { FAIL("compress"); goto cleanup; }
|
||||
|
||||
rc = zuptsdk_verify(ctx, arc, arc_sz, NULL, NULL);
|
||||
if (rc != ZUPTSDK_OK) { FAIL("verify"); zuptsdk_free(arc); goto cleanup; }
|
||||
|
||||
zuptsdk_archive_info_t *info = NULL;
|
||||
rc = zuptsdk_archive_info_read(ctx, arc, arc_sz, &info);
|
||||
if (rc != ZUPTSDK_OK) { FAIL("info read"); zuptsdk_free(arc); goto cleanup; }
|
||||
|
||||
int major = zuptsdk_archive_info_format_major(info);
|
||||
int enc = zuptsdk_archive_info_is_encrypted(info);
|
||||
const char *uuid = zuptsdk_archive_info_uuid(info);
|
||||
|
||||
if (major < 1 || enc != 0 || !uuid || strlen(uuid) != 36) {
|
||||
fprintf(stderr, "[major=%d enc=%d uuid=%s] ", major, enc, uuid ? uuid : "NULL");
|
||||
FAIL("info fields wrong");
|
||||
zuptsdk_archive_info_destroy(info);
|
||||
zuptsdk_free(arc);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
zuptsdk_archive_info_destroy(info);
|
||||
zuptsdk_free(arc);
|
||||
PASS();
|
||||
|
||||
cleanup:
|
||||
zuptsdk_options_destroy(opts);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
}
|
||||
|
||||
static void test_corrupted_archive(void) {
|
||||
TEST("verify rejects corrupted archive");
|
||||
zuptsdk_ctx_t *ctx = NULL;
|
||||
CHECK(zuptsdk_ctx_create(&ctx), "ctx");
|
||||
zuptsdk_options_t *opts = NULL;
|
||||
zuptsdk_options_create(&opts);
|
||||
|
||||
/* Build a larger archive so flipping a byte hits compressed data,
|
||||
* not a zero-padded header field. */
|
||||
size_t big_sz = (sizeof(TEST_DATA) - 1) * 100;
|
||||
uint8_t *big = (uint8_t *)malloc(big_sz);
|
||||
if (!big) { FAIL("alloc"); goto cleanup; }
|
||||
for (size_t i = 0; i < 100; i++)
|
||||
memcpy(big + i * (sizeof(TEST_DATA) - 1), TEST_DATA, sizeof(TEST_DATA) - 1);
|
||||
|
||||
uint8_t *arc = NULL; size_t arc_sz = 0;
|
||||
int rc = zuptsdk_compress_buffer(ctx, opts, "c.txt", big, big_sz,
|
||||
NULL, NULL, &arc, &arc_sz);
|
||||
free(big);
|
||||
if (rc != ZUPTSDK_OK) { FAIL("compress"); goto cleanup; }
|
||||
|
||||
/* Flip a byte two-thirds through, in the real compressed data region */
|
||||
if (arc_sz > 200) arc[(arc_sz * 2) / 3] ^= 0xFF;
|
||||
|
||||
rc = zuptsdk_verify(ctx, arc, arc_sz, NULL, NULL);
|
||||
if (rc == ZUPTSDK_OK) {
|
||||
FAIL("verify accepted corrupted archive");
|
||||
zuptsdk_free(arc);
|
||||
goto cleanup;
|
||||
}
|
||||
zuptsdk_free(arc);
|
||||
PASS();
|
||||
|
||||
cleanup:
|
||||
zuptsdk_options_destroy(opts);
|
||||
zuptsdk_ctx_destroy(ctx);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
fprintf(stderr, "\n═══════════════════════════════════════════════════════════════\n");
|
||||
fprintf(stderr, " libzuptsdk %s — roundtrip test suite\n", zuptsdk_version_string());
|
||||
fprintf(stderr, "═══════════════════════════════════════════════════════════════\n\n");
|
||||
|
||||
test_version();
|
||||
test_strerror();
|
||||
test_context();
|
||||
test_options();
|
||||
test_secure_buf();
|
||||
test_compress_buffer_plain();
|
||||
test_compress_buffer_password();
|
||||
test_compress_buffer_wrong_password();
|
||||
test_keypair_pq();
|
||||
test_verify_and_info();
|
||||
test_corrupted_archive();
|
||||
|
||||
fprintf(stderr, "\n═══════════════════════════════════════════════════════════════\n");
|
||||
fprintf(stderr, " Results: %d passed, %d failed\n", g_pass, g_fail);
|
||||
fprintf(stderr, "═══════════════════════════════════════════════════════════════\n\n");
|
||||
return g_fail == 0 ? 0 : 1;
|
||||
}
|
||||
98
sdk/zuptsdk.map
Normal file
98
sdk/zuptsdk.map
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Copyright (c) 2026 Cristian Cezar Moisés
|
||||
# libzuptsdk symbol version script
|
||||
# All public symbols are namespaced under ZUPTSDK_1.0.
|
||||
# Anything not listed here is hidden via `local: *;` — guaranteed no leakage
|
||||
# of internal zupt_*, vv_*, or other symbols from the static libraries we
|
||||
# link against.
|
||||
#
|
||||
# Adding new symbols in v1.x: add a new ZUPTSDK_1.x block that inherits from
|
||||
# the previous one. NEVER modify or remove symbols within an existing block.
|
||||
#
|
||||
# Verification: after build, `nm -D --defined-only libzuptsdk.so | grep ' T '`
|
||||
# must show ONLY symbols listed below.
|
||||
|
||||
ZUPTSDK_1.0 {
|
||||
global:
|
||||
# Version
|
||||
zuptsdk_version_string;
|
||||
zuptsdk_version_check;
|
||||
|
||||
# Errors
|
||||
zuptsdk_strerror;
|
||||
zuptsdk_last_error_detail;
|
||||
|
||||
# Global config
|
||||
zuptsdk_set_allocator;
|
||||
|
||||
# Context
|
||||
zuptsdk_ctx_create;
|
||||
zuptsdk_ctx_destroy;
|
||||
zuptsdk_ctx_set_threads;
|
||||
zuptsdk_ctx_set_progress_callback;
|
||||
zuptsdk_ctx_set_log_callback;
|
||||
|
||||
# Options
|
||||
zuptsdk_options_create;
|
||||
zuptsdk_options_destroy;
|
||||
zuptsdk_options_set_codec;
|
||||
zuptsdk_options_set_level;
|
||||
zuptsdk_options_set_dedup;
|
||||
zuptsdk_options_set_solid;
|
||||
zuptsdk_options_set_block_size;
|
||||
zuptsdk_options_set_max_decompressed;
|
||||
|
||||
# Secure buffers
|
||||
zuptsdk_secure_buf_create;
|
||||
zuptsdk_secure_buf_destroy;
|
||||
zuptsdk_secure_buf_get;
|
||||
zuptsdk_secure_buf_from_data;
|
||||
|
||||
# Keys
|
||||
zuptsdk_keypair_generate;
|
||||
zuptsdk_keypair_destroy;
|
||||
zuptsdk_keypair_save_private;
|
||||
zuptsdk_keypair_save_public;
|
||||
zuptsdk_privkey_load;
|
||||
zuptsdk_privkey_destroy;
|
||||
zuptsdk_pubkey_load;
|
||||
zuptsdk_pubkey_destroy;
|
||||
zuptsdk_privkey_get_public;
|
||||
|
||||
# Compress / decompress (buffer)
|
||||
zuptsdk_compress_files;
|
||||
zuptsdk_compress_buffer;
|
||||
zuptsdk_extract_to_dir;
|
||||
zuptsdk_extract_buffer;
|
||||
|
||||
# Compress / decompress (stream)
|
||||
zuptsdk_compress_stream;
|
||||
zuptsdk_decompress_stream;
|
||||
|
||||
# Verify / info
|
||||
zuptsdk_verify;
|
||||
zuptsdk_archive_info_read;
|
||||
zuptsdk_archive_info_destroy;
|
||||
zuptsdk_archive_info_format_major;
|
||||
zuptsdk_archive_info_format_minor;
|
||||
zuptsdk_archive_info_uuid;
|
||||
zuptsdk_archive_info_created_unix;
|
||||
zuptsdk_archive_info_size;
|
||||
zuptsdk_archive_info_block_count;
|
||||
zuptsdk_archive_info_is_encrypted;
|
||||
zuptsdk_archive_info_is_pq_hybrid;
|
||||
zuptsdk_archive_info_is_solid;
|
||||
zuptsdk_archive_info_is_dedup;
|
||||
zuptsdk_archive_info_is_disk_image;
|
||||
|
||||
# Disk
|
||||
zuptsdk_disk_backup;
|
||||
zuptsdk_disk_restore;
|
||||
|
||||
# Misc
|
||||
zuptsdk_free;
|
||||
zuptsdk_secure_zero;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
Loading…
Reference in a new issue