# Zero-Length OCTET STRING / BIT STRING Attribute Fixture

**Date:** 2026-06-19
**Branch:** fix/get-hex-null-termination
**Related:** PR #65, Issue #63

## Context

`get_hex()` in `PKCS12.xs` previously left `*attribute` without a NUL terminator when called with a zero-length input. The fix (PR #65) adds `*out = '\0'` after the loop and bumps both `Renew` call sites from `length * 4` to `length * 4 + 1`. Copilot review requested a regression test covering the zero-length path. No existing fixture carries a zero-length OCTET STRING or BIT STRING bag attribute, and the standard `openssl pkcs12` CLI cannot produce one. A pre-generated fixture and dedicated test are needed.

## Generation Script

**File:** `scripts/generate-zero-length-attr-fixture.pl`

**Steps:**
1. Load `certs/test-cert.pem` and `certs/test-key.pem` (existing fixtures, no new cert generation)
2. Call `Crypt::OpenSSL::PKCS12->create()` to produce a baseline PKCS12 in memory
3. Decode the raw DER with `Convert::ASN1` using the PKCS12 / SafeContents schema
4. Locate the first certificate bag's `bagAttributes` sequence
5. Append two new attributes:
   - OID `1.2.3.100` → zero-length OCTET STRING (`04 00`)
   - OID `1.2.3.101` → zero-length BIT STRING (`03 01 00`)
6. Re-encode and write to `certs/zero-length-attrs.p12`

The script is a one-off developer tool — it is not run during `make test`. Re-run only if `test-cert.pem` or `test-key.pem` change.

**Dependency:** `Convert::ASN1` (added to `cpanfile` as a develop dependency).

## Fixture

**File:** `certs/zero-length-attrs.p12`

Pre-generated by the script above and committed to the repository. Contains one certificate bag with two custom bag attributes: a zero-length OCTET STRING at OID `1.2.3.100` and a zero-length BIT STRING at OID `1.2.3.101`. Password: `test`.

## Test File

**File:** `t/pkcs12-info-zero-length-attributes.t`

1. Load `certs/zero-length-attrs.p12` with password `test`
2. Call `info()` — assert no croak, returns non-empty string
3. Call `info_as_hash()` — assert no croak, returns a hashref
4. Locate bag attribute for OID `1.2.3.100` (OCTET STRING) — assert it is defined and exactly `""` (empty string, not garbage)
5. Locate bag attribute for OID `1.2.3.101` (BIT STRING) — same assertion

No heap-poisoning loop required (unlike the UTF8STRING test) — the bug causes hard UB on zero-length, so a single call is sufficient. No OpenSSL version skips needed — the fixture uses standard PKCS12 with no secret bags or RC2.

## Documentation

**File:** `scripts/README.md`

Documents each script in `scripts/`. Entry for `generate-zero-length-attr-fixture.pl` explains:
- Why the standard `openssl pkcs12` CLI cannot produce zero-length bag attributes
- Which OIDs and attribute types are embedded
- That `certs/zero-length-attrs.p12` is pre-committed and only needs regenerating if the base cert/key fixtures change

## Dependencies

Add to `cpanfile`:

```
on 'develop' => sub {
    requires 'Convert::ASN1';
};
```

`Convert::ASN1` is only needed to run the generation script, not for the module itself or its test suite.

## Verification

1. Run `perl scripts/generate-zero-length-attr-fixture.pl` — confirms `certs/zero-length-attrs.p12` is written without error
2. Run `prove -lv t/pkcs12-info-zero-length-attributes.t` — all assertions pass
3. Run full suite `prove -lr -l -b -I inc t` — all 357+ tests still pass
4. Confirm `openssl pkcs12 -info -in certs/zero-length-attrs.p12 -passin pass:test -noout` does not error
