NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Crypto for Laravel

ASN.1 / DER decoder

Asn1\DerDecoder is a strict X.690 reader. It exists because openssl_x509_parse() pretty-prints unknown extensions into lossy text, so anything that needs an extension’s actual bytes — an Apple WebAuthn nonce, say — needs a real DER walk:

use RoundlyConsulting\Crypto\Facades\Crypto;

// The Apple WebAuthn nonce extension: SEQUENCE { [1] { OCTET STRING nonce } }
$element = Crypto::derDecoder()->decode($certificate->extension('1.2.840.113635.100.8.2')?->der ?? '');

$nonce = $element->tagged(1)?->children()[0]->octetString();

Or call the classes the facade fronts directly:

use RoundlyConsulting\Crypto\Asn1\DerDecoder;

// The Apple WebAuthn nonce extension: SEQUENCE { [1] { OCTET STRING nonce } }
$element = (new DerDecoder)->decode($certificate->extension('1.2.840.113635.100.8.2')?->der ?? '');

$nonce = $element->tagged(1)?->children()[0]->octetString();

$element->children();          // list<DerElement>, in encoding order
$element->oid();               // '1.2.840.113635.100.8.2' — dotted decimal
$element->integer();           // int, or raw bytes when wider than 64 bits
$element->boolean();           // DER's 0x00 / 0xFF only
$element->isNull();

(new DerDecoder)->decodeFirst($bytes);   // element + bytesRead, for walking a run of TLVs

decode() reads exactly one element and treats trailing bytes as an error; decodeFirst() returns a DerResult with the element and bytesRead, for walking a run of TLVs. The decoder is a container singleton and is also reachable as Crypto::derDecoder().

DerElement readers

MemberNotes
class / tag / constructed / contentsThe TagClass (Universal, Application, ContextSpecific, Private), the tag number (the high-tag form is supported), the form and the raw contents octets.
children()Child elements of a constructed element, in encoding order; throws on a primitive.
tagged($tag)The first child with that context-specific tag — how an optional [n] field is located.
oid()The OBJECT IDENTIFIER in dotted decimal.
integer()An int, or raw two’s-complement bytes when wider than 64 bits.
octetString()The OCTET STRING contents, verbatim.
boolean()DER’s 0x00 / 0xFF only.
isNull()Whether the element is NULL — a predicate that reports rather than throws.

Constructed elements are parsed eagerly, and each typed reader refuses a tag it does not admit (MalformedDerException::unexpectedTag) rather than guessing.

Strictness and bounds

  • Indefinite lengths (that is BER), non-minimal length or tag encodings and the reserved 0xFF length form are rejected.
  • Padded INTEGERs and OID subidentifiers, BER-lax BOOLEANs and NULLs carrying content are rejected.
  • A declared length past the end of the buffer is rejected — never an over-read.
  • Trailing bytes after the top-level element and a constructed/primitive form mismatch are rejected.
  • Work is bounded three ways: 64 KiB of input, 16 levels of nesting and 4096 elements.

Everything hostile is a MalformedDerException, never a PHP warning, and the decoder is fuzz-tested. It is a parser, never a trust store — what an extension’s contents mean is your call, made in your code.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.