Crypt-OpenSSL-PKCS12

 view release on metacpan or  search on metacpan

CLAUDE.md  view on Meta::CPAN


## Architecture

### XS Layer (`PKCS12.xs`)

The XS file does all the heavy lifting:
- Compatibility macros at the top handle API differences between OpenSSL < 1.1.0 and >= 1.1.0
- OpenSSL 3.x requires loading providers (`legacy` and `deflt` globals); this is handled via `#if OPENSSL_VERSION_NUMBER >= 0x30000000L`
- `_load_pkey()` and `_load_cert_chain()` accept either a PEM string (detected by `"----"` prefix) or a file path — this dual-input pattern is used by `create()` and `create_as_string()`
- `CHECK_OPEN_SSL(p_result)` macro wraps OpenSSL calls and croaks with an error message from `ERR_reason_error_string()` on failure
- `certificate()`, `private_key()`, `ca_certificate()` silently return an empty string on wrong password or missing content — the return value of `dump_certs_keys_p12()` is ignored, so these never croak on decryption failure
- `legacy_support()` checks the global `legacy` pointer (set only by constructors); may return false before any object is constructed even if the provider is loadable
- `as_string()` takes no arguments — it serializes the in-memory PKCS12 object directly with no password

### Distribution Management (`dist.ini`)

The distribution uses Dist::Zilla with `MakeMaker::Awesome`. Key points:
- `Makefile.PL` and `cpanfile` are **generated** by `dzil build` — edit `dist.ini` and `maint/Makefile_header.PL` instead, not `Makefile.PL` directly
- Bumping `$VERSION` in `PKCS12.pm` alone is not enough — `Makefile.PL` hardcodes the version from the last `dzil build`/`dzil test`. A stale one causes `object version X does not match bootstrap parameter Y` at runtime; always `dzil build` after a...
- Version is sourced from `PKCS12.pm` via `[VersionFromMainModule]`
- `README.md` is auto-generated from the POD in `PKCS12.pm`

PKCS12.pm  view on Meta::CPAN

Crypt::OpenSSL::PKCS12 - Perl extension to OpenSSL's PKCS12 API.

=head1 SYNOPSIS

  use Crypt::OpenSSL::PKCS12;

  my $pass   = "your password";
  my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_file('cert.p12');

  print $pkcs12->certificate($pass);
  print $pkcs12->private_key($pass);

  if ($pkcs12->mac_ok($pass)) {
    # MAC verification passed
  }

  # Creating a file
  $pkcs12->create('test-cert.pem', 'test-key.pem', $pass, 'out.p12', 'friendly name');


  # Creating a string

PKCS12.pm  view on Meta::CPAN

C<-----BEGIN CERTIFICATE-----> / C<-----END CERTIFICATE-----> headers).
C<$pass> is required when the PKCS12 file is password-protected. Returns an
empty string if the password is wrong or no client certificate is present.

=item * ca_certificate( [C<$pass>] )

Returns any CA certificates in the chain as a concatenated PEM string.
Returns an empty string if no CA certificates are present. C<$pass> is
required when the PKCS12 file is password-protected.

=item * private_key( [C<$pass>] )

Returns the private key as a PEM-encoded string. C<$pass> is required when
the PKCS12 file is password-protected. Returns an empty string if no private
key is present or if decryption fails (wrong password).

=item * as_string( )

Returns the PKCS12 structure as a raw binary DER string. Useful for writing
to a file or transmitting over a network without touching the filesystem.
The in-memory structure is serialized as-is; no password is needed or accepted.

PKCS12.xs  view on Meta::CPAN

        RETVAL = newSVpvn("",0);

  dump_certs_keys_p12(aTHX_ bio, pkcs12, pwd_str, (int)pwd_len, CACERTS|NOKEYS, NULL, NULL);

  RETVAL = extractBioString(aTHX_ bio);

  OUTPUT:
  RETVAL

SV*
private_key(pkcs12, pwd = &PL_sv_undef)
  Crypt::OpenSSL::PKCS12 pkcs12
  SV *pwd

  PREINIT:
  BIO *bio;
  STRLEN pwd_len = 0;
  const char *pwd_str = "";

  CODE:

docs/superpowers/plans/2026-06-25-pod-documentation-improvements.md  view on Meta::CPAN


---

### Task 3: Document certificate/key methods and `changepass`

**Files:**
- Modify: `PKCS12.pm` (POD section)

**Interfaces:**
- Consumes: POD from Task 2
- Produces: `certificate()`, `ca_certificate()`, `private_key()`, `as_string()`, `mac_ok()`, `changepass()` all document return format, password parameter behaviour, and known caveats

- [ ] **Step 1: Expand `certificate()` and `ca_certificate()`**

Find:
```pod
=item * certificate( [C<$pass>] )

Get the Base64 representation of the certificate.

=item * ca_certificate( [C<$pass>] )

docs/superpowers/plans/2026-06-25-pod-documentation-improvements.md  view on Meta::CPAN

C<$pass> is required when the PKCS12 file is password-protected. Croaks on
wrong password or missing certificate.

=item * ca_certificate( [C<$pass>] )

Returns any CA certificates in the chain as a concatenated PEM string.
Returns C<undef> if no CA certificates are present. C<$pass> is required
when the PKCS12 file is password-protected.
```

- [ ] **Step 2: Expand `private_key()`**

Find:
```pod
=item * private_key( [C<$pass>] )

Get the Base64 representation of the private key.
```
Replace with:
```pod
=item * private_key( [C<$pass>] )

Returns the private key as a PEM-encoded string. C<$pass> is required when
the PKCS12 file is password-protected. Croaks if no private key is present
or if decryption fails.
```

- [ ] **Step 3: Expand `as_string()`**

Find:
```pod

docs/superpowers/plans/2026-06-25-pod-documentation-improvements.md  view on Meta::CPAN


```bash
perldoc PKCS12.pm | grep -A8 "changepass"
```
Expected: note about OpenSSL 3.x appears.

- [ ] **Step 7: Commit**

```bash
git add PKCS12.pm
git commit -m "docs: document certificate, private_key, mac_ok, changepass methods"
```

---

### Task 4: Document `create`, `create_as_string`, `info`, `info_as_hash`

**Files:**
- Modify: `PKCS12.pm` (POD section)

**Interfaces:**

docs/superpowers/plans/2026-06-25-pod-documentation-improvements.md  view on Meta::CPAN

| Version 1.95 → 1.97 | Task 1 |
| `represenation` typo | Task 1 |
| `Verifiy` typo | Task 1 |
| `$pksc12_data` typo | Task 1 |
| Unclosed `if` in SYNOPSIS | Task 1 |
| `new()` empty | Task 2 |
| `legacy_support()` thin | Task 2 |
| `new_from_string/file()` thin | Task 2 |
| `changepass()` OpenSSL 3.x caveat | Task 3 |
| `certificate()` / `ca_certificate()` format | Task 3 |
| `private_key()` format | Task 3 |
| `as_string()` thin | Task 3 |
| `mac_ok()` thin | Task 3 |
| `create()` return value | Task 4 |
| `create_as_string()` return value | Task 4 |
| dualvar unexplained in `info_as_hash()` | Task 4 |
| Exported constants undocumented | Task 5 |
| DIAGNOSTICS empty | Task 5 |
| DEPENDENCIES Perl 5.8 stale | Task 5 |
| SEE ALSO OpenSSL link stale | Task 5 |
| PR creation | Task 6 |

t/pkcs12-from-scratch.t  view on Meta::CPAN

# make PKCS12 object from string
my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_string($certdata);

# run below code that is completely taken from the test pkcs12.t
ok($pkcs12, 'PKCS object created');

my $pemcert = $pkcs12->certificate($pass);

ok($pemcert, 'PEM certificate created');

my $pemkey = $pkcs12->private_key($pass);

ok($pemkey, 'Asserting PEM key');

ok($pkcs12->mac_ok($pass), 'Asserting mac');

ok($pkcs12->as_string, 'Asserting PKCS12 as string');

SKIP: {
    # PKCS12_newpass() is fundamentally broken for PBES2-encrypted PKCS12
    # files (the default since OpenSSL 1.1/3.x): confirmed upstream at

t/pkcs12-string.t  view on Meta::CPAN

# make PKCS12 object from string
my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_string($certdata);

# run below code that is completely taken from the test pkcs12.t
ok($pkcs12, 'PKCS object created');

my $pemcert = $pkcs12->certificate($pass);

ok($pemcert, 'PEM certificate created');

my $pemkey = $pkcs12->private_key($pass);

ok($pemkey, 'Asserting PEM key');

ok($pkcs12->mac_ok($pass), 'Asserting mac');

ok($pkcs12->as_string, 'Asserting PKCS12 as string');

SKIP: {
    # PKCS12_newpass() is fundamentally broken for PBES2-encrypted PKCS12
    # files (the default since OpenSSL 1.1/3.x): confirmed upstream at

t/pkcs12.t  view on Meta::CPAN

ok($pkcs12, 'PKCS object created');

my $pemcert = $pkcs12->certificate($pass);

ok($pemcert, 'PEM certificate created');

my $cacert = $pkcs12->ca_certificate($pass);

ok($cacert, 'CA certificate created');

my $pemkey = $pkcs12->private_key($pass);

ok($pemkey, 'Asserting PEM key');

ok($pkcs12->mac_ok($pass), 'Asserting mac');

ok($pkcs12->as_string, 'Asserting PKCS12 as string');

SKIP: {
    # PKCS12_newpass() is fundamentally broken for PBES2-encrypted PKCS12
    # files (the default since OpenSSL 1.1/3.x): confirmed upstream at



( run in 1.951 second using v1.01-cache-2.11-cpan-8dfa8b56332 )