gmcrypto-c 1.3.0

C ABI for gmcrypto-core — cdylib + staticlib exposing SM2/SM3/SM4/HMAC/PBKDF2 to C / C++ / Python / Go / Zig callers via opaque handles
Documentation
# gmcrypto-c

C ABI for `gmcrypto-core` — pure-Rust SM2 / SM3 / SM4 SDK exposed to
C / C++ / Python (cffi) / Go (cgo) / Zig / Ruby (FFI) callers via a
cdylib + staticlib and a cbindgen-generated header.

The library is a thin shim over [`gmcrypto-core`](../gmcrypto-core);
every cryptographic operation runs in the dudect-gated Rust core,
not in this crate. The C ABI is the language-binding surface only.

## Build

```bash
# cdylib (libgmcrypto_c.so / .dylib / .dll) + staticlib (.a / .lib) +
# rlib (for Rust consumers).
cargo build -p gmcrypto-c --release
```

Output artifacts land in `target/release/`:

- `libgmcrypto_c.so` (Linux) / `libgmcrypto_c.dylib` (macOS) /
  `gmcrypto_c.dll` (Windows) — shared library.
- `libgmcrypto_c.a` (Linux/macOS) / `gmcrypto_c.lib` (Windows) —
  static library.

## Cipher-mode coverage (complete, always-on)

The C ABI is **complete and always-on**: a default `cargo build -p gmcrypto-c`
exports the full surface — SM2 / SM3 / SM4-ECB+CBC / HMAC-SM3 / PBKDF2-HMAC-SM3
**plus** the symmetric-cipher modes below. As of **v0.23** the AEAD/XTS symbols
are no longer behind opt-in cargo features (the former `sm4-aead` / `sm4-xts`
forwarding features were removed), so the committed
[`include/gmcrypto.h`](include/gmcrypto.h) matches the default build exactly —
no feature flags are needed.

| Mode | Symbols | Example |
|---|---|---|
| SM4-GCM / SM4-CCM AEAD (single-shot + streaming SM4-GCM) | `gmcrypto_sm4_gcm_*` / `gmcrypto_sm4_ccm_*` | [`examples/sm4_gcm_streaming.c`]examples/sm4_gcm_streaming.c |
| SM4-XTS tweakable disk/sector mode (GB/T 17964-2021): single-shot + in-place multi-sector | `gmcrypto_sm4_xts_{encrypt,decrypt}` / `gmcrypto_sm4_xts_{encrypt,decrypt}_sectors` | [`examples/sm4_xts_sector.c`]examples/sm4_xts_sector.c, [`examples/sm4_xts_multisector.c`]examples/sm4_xts_multisector.c |
| SM2 key exchange (GM/T 0003.3) with key confirmation (v1.2): two opaque role handles; `_confirm`/`_finish` consume + free; `_with_rng` variants for caller-supplied randomness | `gmcrypto_sm2_kx_initiator_{new,new_with_rng,confirm,free}` / `gmcrypto_sm2_kx_responder_{new,respond,respond_with_rng,finish,free}` | [`examples/sm2_key_exchange.c`]examples/sm2_key_exchange.c |

**SM2 key exchange** notes: the initiator handle is created already holding
its ephemeral (`_new` writes `R_A`); the agreed key is written to caller
memory and **the caller owns wiping it**; every failure (off-curve peer `R`,
tag mismatch, misuse ordering, null, RNG failure) is the single
`GMCRYPTO_ERR`. `id_len == 0` selects the GM/T default ID
`"1234567812345678"`.

```bash
cargo build -p gmcrypto-c --release   # the complete ABI — no feature flags
```

**SM4-XTS** takes a 32-byte key (`Key1 ‖ Key2`; `Key1 == Key2` is
rejected) and a 16-byte tweak (the per-sector data-unit identifier,
caller-unique per key). It is length-preserving and **confidentiality
only — it does not authenticate**; use an AEAD mode if you need
integrity. Sizes are exported as `GMCRYPTO_SM4_XTS_KEY_SIZE` (32) and
`GMCRYPTO_SM4_BLOCK_SIZE` (16).

The **`*_sectors`** variants encrypt/decrypt a contiguous run of equal-size
sectors **in place** (`buf` is both input and output; `buf_len` must be a
whole multiple of `sector_size`), deriving sector `i`'s tweak as
little-endian-128(`start_sector + i`) — the standard disk-XTS LBA convention.
`start_sector` is a `uint64_t`. **Sector numbers must be unique within an
XTS-key namespace** — do not encrypt multiple devices / partitions / snapshots
under one key all starting at sector 0 (use absolute LBAs or a per-device
key); reuse leaks block-equality structure. On `GMCRYPTO_ERR` (bad
`sector_size`, bad `buf_len` multiple, weak key, null) `buf` is untouched; a
zero-length run is a vacuous success (but the key is still validated).

## Header

The committed header at [`include/gmcrypto.h`](include/gmcrypto.h)
is the v0.4 C ABI contract. To regenerate after editing the FFI
surface in [`src/lib.rs`](src/lib.rs):

```bash
cargo build -p gmcrypto-c --features regen-header
```

CI verifies the committed header is in sync via `git diff --exit-code`
on every PR.

## Linking against C

```bash
# Compile a C consumer against the shared library.
gcc -I crates/gmcrypto-c/include \
    -L target/release -lgmcrypto_c \
    examples/sm2_sign.c -o sm2_sign

# Run (set LD_LIBRARY_PATH on Linux / DYLD_LIBRARY_PATH on macOS):
LD_LIBRARY_PATH=target/release ./sm2_sign
```

Static linking:

```bash
gcc -I crates/gmcrypto-c/include \
    examples/sm2_sign.c \
    target/release/libgmcrypto_c.a \
    -o sm2_sign-static
./sm2_sign-static
```

## Failure-mode invariant

Every entry point returning `int` follows the workspace
failure-mode discipline:

- `0` (= `GMCRYPTO_OK`) on success.
- Non-zero (= `GMCRYPTO_ERR` etc.) on failure. **All non-zero
  returns are equivalent**; do not distinguish failure modes by
  the specific value.

Per Q4.8 in [`docs/v0.4-scope.md`](../../docs/v0.4-scope.md):
distinguishing failure modes (wrong password vs. malformed PEM
vs. inner-key-parse failure) would introduce a password-oracle
attack surface. C consumers MUST treat all non-zero returns as
opaque.

## Output buffer convention

Variable-length output (signatures, ciphertexts, PKCS#8 blobs)
uses the `(out_ptr, out_capacity, out_actual_len)` shape per
Q4.13:

```c
size_t actual = 0;
int rc = gmcrypto_sm2_sign(
    key, NULL, 0,         /* signer_id = DEFAULT */
    msg, msg_len,
    sig_buf, sizeof sig_buf,
    &actual);
if (rc != GMCRYPTO_OK) {
    if (actual > sizeof sig_buf) {
        /* too-small buffer; `actual` carries the required length */
    } else {
        /* other failure */
    }
}
```

On too-small buffer: `rc != 0`, `*out_actual_len` set to the
required length, `out_ptr` unmodified.

## Handle ownership

Opaque handles (`gmcrypto_sm2_privkey_t*`, etc.) are heap-allocated
via `Box::into_raw`. Pair every `_new` with exactly one `_free`.
`_free(NULL)` is a no-op (mirrors C's `free()`).

Private-key handles inherit `ZeroizeOnDrop` from gmcrypto-core; the
inner scalar / round keys are zeroized before the heap slot is
freed.

## RNG sourcing

`gmcrypto_sm2_sign`, `gmcrypto_sm2_encrypt`, and
`gmcrypto_sm2_privkey_to_pkcs8` source randomness from
`getrandom::SysRng` internally. C consumers do not pass an RNG
callback in v0.4; a "register custom RNG" entry point may land
post-v0.4 if a use case surfaces.

## `unsafe_code` posture

Per Q4.7 in the scope doc: `gmcrypto-c` cannot satisfy
`unsafe_code = "forbid"` because raw-pointer dereferencing and
`Box::from_raw` are inherent to FFI. Every `unsafe` block carries
a `// SAFETY:` comment naming the caller-side preconditions. The
core crate [`gmcrypto-core`](../gmcrypto-core) itself stays
`unsafe_code = "forbid"`.

## SemVer

The C ABI is the v0.4 baseline. Signature changes in v0.4.x are
breaking ABI changes; v0.5 may break the C ABI without a major
Rust-side bump (since `gmcrypto-c` is a separate crate from
`gmcrypto-core`).

`gmcrypto_sm2_privkey_to_sec1_be` is NOT SemVer-stable across
v0.4.x per Q4.19 — caller MUST zeroize the returned scalar bytes;
documented in the header.