synta 0.2.1

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# Testing Guide

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Test Suite Overview]#test-suite-overview
- [Running Tests]#running-tests
  - [All tests at once]#all-tests-at-once
  - [Individual crate tests]#individual-crate-tests
  - [Merkle Tree Certificate tests]#merkle-tree-certificate-tests
  - [Python binding tests]#python-binding-tests
  - [C FFI tests]#c-ffi-tests
  - [RFC 5280 compliance (x509-limbo)]#rfc-5280-compliance-x509-limbo
  - [PKCS#11 / HSM Integration Test (kryoptic)]#pkcs11-hsm-integration-test-kryoptic
    - [Prerequisites]#prerequisites
    - [Build-time discovery]#build-time-discovery
    - [Running the test]#running-the-test
    - [Graceful skip behaviour]#graceful-skip-behaviour
  - [PKCS#11 Python Management Tests]#pkcs11-python-management-tests
    - [Tests that pass without a live PKCS#11 token (9 tests)]#tests-that-pass-without-a-live-pkcs11-token-9-tests
    - [Tests that skip gracefully when no PKCS#11 module is available (3 tests)]#tests-that-skip-gracefully-when-no-pkcs11-module-is-available-3-tests
    - [Running the Python management tests]#running-the-python-management-tests
- [Test Vectors]#test-vectors
- [Serde Tests]#serde-tests
- [Writing New Tests]#writing-new-tests
  - [Rust unit / integration tests]#rust-unit-integration-tests
  - [Python tests]#python-tests
  - [C FFI tests]#c-ffi-tests-1
- [Code Coverage]#code-coverage
- [Fuzzing]#fuzzing
- [Continuous Integration]#continuous-integration
- [See Also]#see-also

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

<!-- toc -->
<!-- /toc -->

This guide explains how to run, extend, and debug the test suites across all
Synta crates and language bindings.  For CI configuration see
[Contributing](contribution.md#running-ci-locally); for architecture background
see [System Architecture](system-architecture.md).

---

## Test Suite Overview

| Suite | Location | What it covers |
|-------|----------|---------------|
| Core DER/BER unit tests | `tests/*.rs` | Decoder, encoder, all ASN.1 types, tag/length, roundtrip, serde, BER indefinite-length |
| Certificate tests | `synta-certificate/tests/` | X.509/CRL/CSR/OCSP parsing, PKCS#7/12, algorithm IDs, owned types |
| Codegen tests | `synta-codegen/tests/` | ASN.1 schema parsing and Rust code generation |
| Kerberos tests | `synta-krb5/tests/` | Kerberos V5 ASN.1 structures |
| MTC tests | `synta-mtc/tests/` | Merkle Tree Certificate validation and property tests |
| x509-limbo | `synta-x509-verification/tests/limbo/` | RFC 5280 path validation compliance (~39 MB harness) |
| Python binding tests | `tests/python/` | Full Python API surface via pytest |
| C FFI tests | `tests/c/` | C API bindings via `make` |

---

## Running Tests

### All tests at once

```bash
./contrib/ci/local-ci.sh test
```

This runs `cargo test --workspace --all-features` on the stable, beta, and
nightly toolchains in sequence.

### Individual crate tests

```bash
# Core ASN.1 parser
cargo test -p synta

# X.509 / PKI
cargo test -p synta-certificate

# Code generator
cargo test -p synta-codegen --all-features

# Kerberos types
cargo test -p synta-krb5

# Merkle Tree Certificates
cargo test -p synta-mtc

# RFC 5280 path validation (x509-limbo)
cargo test -p synta-x509-verification
```

### Merkle Tree Certificate tests

```bash
cargo test -p synta-mtc
```

`synta-mtc` has several test targets with different purposes:

| File | Command | Purpose |
|------|---------|---------|
| `tests/security_audit.rs` | `cargo test -p synta-mtc --test security_audit` | 27 security-focused tests (DoS, manipulation, separation) |
| `tests/property_tests.rs` | `cargo test -p synta-mtc --test property_tests` | 13 property-based tests (custom harness) |
| `tests/test_vectors.rs` | `cargo test -p synta-mtc --test test_vectors` | 17 test vectors |
| `tests/roundtrip.rs` | `cargo test -p synta-mtc --test roundtrip` | ASN.1 encode/decode round-trips |
| `tests/integration.rs` | `cargo test -p synta-mtc --test integration` | End-to-end validator tests |
| `src/` (unit) | `cargo test -p synta-mtc --lib` | 203 unit tests inline with source |

Key functions with dedicated test coverage:

- `verify_subtree_inclusion_proof` — compact proof-path consistency (spec §4.3.3);
  translates absolute leaf index to subtree-relative before calling
  `verify_inclusion_proof`.
- Serial number enforcement — `CertificateValidator` checks that
  `TBSCertificate.serialNumber == log_entry_index` and that the serial is at
  least 1 before computing the leaf hash.
- Subtree alignment — `constraint::validate_subtree_range` rejects subtrees
  where `start % BIT_CEIL(end - start) != 0` (spec §4.1).

### Python binding tests

The Python binding must be compiled first.  Use `maturin develop` (recommended
for development) or the CI build step:

```bash
# Quick development cycle
cd synta-python
uv venv
uv pip install maturin pytest
uv run maturin develop
uv run pytest ../tests/python/ -v

# Or use the CI helper (also builds the release .so)
./contrib/ci/local-ci.sh python-test
```

Alternatively, if you have a compiled `.so` in `python/`:

```bash
PYTHONPATH=python python3 -m pytest tests/python/ -v
```

### C FFI tests

Build the shared library first, then run the C test suite:

```bash
cargo build --release -p synta-ffi
make -C tests/c test
```

For memory-error checking:

```bash
make -C tests/c valgrind
```

### RFC 5280 compliance (x509-limbo)

The x509-limbo test vectors are large (~39 MB).  They are checked out
automatically by the test runner if the `tests/limbo/` directory contains the
harness JSON:

```bash
./contrib/ci/local-ci.sh test-limbo
```

---

### PKCS#11 / HSM Integration Test (kryoptic)

The `pkcs11_kryoptic` integration test signs and verifies a CA certificate
using a real PKCS#11 token.  It exercises `BackendPrivateKey::from_pkcs11_uri`
end-to-end and confirms that the resulting signature is valid against the
public key extracted from the token.

#### Prerequisites

- **kryoptic** — a software PKCS#11 token implementation
  (`libkryoptic_pkcs11.so` must be discoverable at build time)
- **pkcs11-tool** at `/usr/bin/pkcs11-tool` (from the `opensc` package)
- **modutil** — optional, required for the NSS backend path
  (from the `nss-tools` package on Fedora/RHEL)

#### Build-time discovery

`synta-certificate/build.rs` probes for kryoptic at build time and sets two
constants:

- `SYNTA_TEST_KRYOPTIC_LIB` — absolute path to `libkryoptic_pkcs11.so`
- `SYNTA_TEST_PKCS11_PROVIDER` — absolute path to the OpenSSL pkcs11-provider shared object

When `SYNTA_TEST_KRYOPTIC_LIB` is empty (kryoptic not found), every test
function prints a skip message and returns without failure.
`SYNTA_TEST_PKCS11_PROVIDER` is checked only in the OpenSSL backend path; it
does not affect the NSS backend test.  The library search path can be
overridden at build time via the `LT_SYS_LIBRARY_PATH` environment variable.

#### Running the test

```bash
# OpenSSL backend
cargo test -p synta-certificate --test pkcs11_kryoptic --features openssl -- --test-threads=1 --nocapture

# NSS backend
cargo test -p synta-certificate --test pkcs11_kryoptic --features nss -- --test-threads=1 --nocapture
```

`--test-threads=1` is **required**.  Both `OPENSSL_CONF` (OpenSSL backend) and
NSS module state (NSS backend) are process-global.  Running test functions in
parallel on separate threads would cause them to race on that global state,
producing spurious failures.

#### Graceful skip behaviour

When kryoptic or `pkcs11-tool` are absent, each `#[test]` function prints a
human-readable skip message (visible with `--nocapture`) and returns
`Ok(())` rather than failing.  This means the test binary exits with status 0,
and CI jobs that run on machines without an HSM do not fail.

---

### PKCS#11 Python Management Tests

`tests/python/test_pkcs11.py` contains 12 tests for the `synta.pkcs11`
Python submodule (the `pkcs11-mgmt` feature).  All tests guard their entire
test module with:

```python
pkcs11 = pytest.importorskip(
    "synta.pkcs11",
    reason="synta compiled without pkcs11-mgmt feature",
)
```

If the submodule is absent (feature not compiled in), the entire file is
skipped rather than failing.

#### Tests that pass without a live PKCS#11 token (9 tests)

These tests exercise module structure and error handling entirely in-process:

| Test | What it checks |
|------|---------------|
| `test_module_all` | `synta.pkcs11.__all__` contains exactly `["SlotInfo", "KeyInfo", "Pkcs11Token", "list_slots"]` |
| `test_slotinfo_class` | `SlotInfo` is callable and present in the module |
| `test_keyinfo_class` | `KeyInfo` is callable and present in the module |
| `test_pkcs11token_class` | `Pkcs11Token` is callable and present in the module |
| `test_list_slots_callable` | `list_slots` is callable |
| `test_token_rejects_non_pkcs11_uri` | `Pkcs11Token("not-a-pkcs11-uri")` raises `ValueError` |
| `test_list_slots_bad_module_path` | `list_slots(module="/nonexistent/path.so")` raises `ValueError` |
| `test_list_slots_relative_module_path` | `list_slots(module="relative/path.so")` raises `ValueError` (relative paths are rejected) |
| `test_token_bad_module_path` | `Pkcs11Token("pkcs11:token=X", module="/nonexistent.so")` raises `ValueError` |

#### Tests that skip gracefully when no PKCS#11 module is available (3 tests)

These tests construct a `Pkcs11Token` with a real URI and inspect its
`__repr__` output.  They call `pytest.skip()` at runtime if no PKCS#11
module can be loaded (e.g. `PKCS11_MODULE_PATH` is unset and no candidate
system path exists):

| Test | What it checks |
|------|---------------|
| `test_pin_redaction_in_repr` | `__repr__` of a `Pkcs11Token` with `?pin-value=1234` shows `pin-value=***` |
| `test_pin_redaction_no_pin` | `__repr__` of a `Pkcs11Token` without a PIN shows no `pin-value` at all |
| `test_pin_redaction_error_message` | Error messages raised when the token is not found do not contain the raw PIN value |

#### Running the Python management tests

```bash
# Using the CI helper (also builds the .so):
./contrib/ci/local-ci.sh python-test

# Directly with pytest (requires a compiled .so in python/):
PYTHONPATH=python python3 -m pytest tests/python/test_pkcs11.py -v

# With a live PKCS#11 module (e.g. SoftHSM2):
PKCS11_MODULE_PATH=/usr/lib64/pkcs11/libsofthsm2.so \
    PYTHONPATH=python python3 -m pytest tests/python/test_pkcs11.py -v
```

---


## Test Vectors

Binary DER/BER test vectors live under `tests/vectors/`.  They are organised
by standard:

```
tests/vectors/
├── pkcs/          # PKCS#7, #8, #10, #12 DER binaries
├── cryptography/  # Vectors from the cryptography Python library
├── certs/         # X.509 certificate DER files
└── README.md      # Origin and licence notes for each vector set
```

When adding a new vector:

1. Place the raw binary under the appropriate subdirectory.
2. Write a test that decodes it and asserts expected field values.
3. Document the origin and licence in `tests/vectors/README.md`.

---

## Serde Tests

Serde support is feature-gated (`--features serde`).  Run serde tests in
isolation to avoid interference from non-serde builds:

```bash
./contrib/ci/local-ci.sh test-serde
# Equivalent to:
cargo test -p synta --features serde
```

---

## Writing New Tests

### Rust unit / integration tests

Place unit tests in a `#[cfg(test)]` module at the bottom of the source file
they test.  Integration tests go in `tests/` at the crate root.

```rust
#[cfg(test)]
mod tests {
    use super::*;
    use synta::{Decoder, Encoding};

    #[test]
    fn decode_my_type() {
        let der = hex::decode("3003020101").unwrap();
        let mut dec = Decoder::new(&der, Encoding::Der);
        let val = dec.decode::<MyType>().unwrap();
        assert_eq!(val.field, 1);
    }
}
```

### Python tests

Python tests use `pytest` and live in `tests/python/`.  Always set
`PYTHONPATH=python` (or use the `maturin develop` environment):

```python
import synta

def test_decode_certificate(der_bytes):
    cert = synta.Certificate.from_der(der_bytes)
    assert cert.serial_number is not None
```

### C FFI tests

C tests are small programs in `tests/c/`.  Each must link against `libcsynta`
(the Makefile handles this) and follow the OpenSSL-style ownership convention:
- `synta_*_parse_der()` returns an owned handle.
- `synta_*_free()` frees it; call it exactly once.
- `get0_*()` getters return borrowed pointers valid while the parent is alive.

---

## Code Coverage

Coverage reports can be generated with `cargo-llvm-cov`:

```bash
cargo llvm-cov --workspace --html --open
# Or for a lcov report:
cargo llvm-cov --workspace --lcov --output-path coverage/lcov.info
```

Existing `.profraw` files in the repository root and `synta-certificate/` are
from previous coverage runs and can be safely deleted.

---

## Fuzzing

The `synta-fuzz` crate provides a structured ASN.1 fuzzer:

```bash
cargo +nightly fuzz run fuzz_target -- -max_len=65536
```

Seeds for the fuzzer live in `tests/fuzz/corpus/`.  Add any DER files that
exercise new code paths to expand the corpus.

---

## Continuous Integration

The full test pipeline is described in [Contributing](contribution.md#running-ci-locally).
Key jobs for testing:

| CI Job | Command | Notes |
|--------|---------|-------|
| `test` | `cargo test --workspace` | stable + beta + nightly |
| `test-certificate` | `cargo test -p synta-certificate` | PKI types |
| `test-codegen` | `cargo test -p synta-codegen --all-features` | ASN.1 codegen |
| `test-krb5` | `cargo test -p synta-krb5` | Kerberos types |
| `test-mtc` | `cargo test -p synta-mtc` | MTC validation |
| `test-limbo` | `cargo test -p synta-x509-verification` | x509-limbo RFC 5280 compliance |
| `test-serde` | `cargo test -p synta --features serde` | Serde roundtrip |
| `python-test` | `pytest tests/python/` | Python bindings |
| `c-test` | `make -C tests/c test` | C FFI |

---

## See Also

- [Contributing]contribution.md — commit conventions, CI setup, code style
- [System Architecture]system-architecture.md — crate dependency graph
- [Codebase Summary]codebase-summary.md — file inventory and test suite table
- [Limitations]limitations.md — known unsupported ASN.1 constructs