CryptX
view release on metacpan or search on metacpan
lib/Crypt/ASN1.pm view on Meta::CPAN
croak "FATAL: asn1_encode: time value missing" unless defined $val;
my $type = $kind eq 'utc' ? 'UTCTIME' : 'GENERALIZEDTIME';
# epoch (all digits, possibly negative)
if ($fmt eq 'epoch' || ($fmt eq '' && $val =~ /^-?\d+$/)) {
my @t = gmtime($val);
my $year = $t[5] + 1900;
if ($kind eq 'utc') {
croak "FATAL: asn1_encode: UTCTIME year out of range: $year (expected 1950..2049)"
if $year < 1950 || $year > 2049;
}
return ($kind eq 'utc')
? sprintf("%02d%02d%02d%02d%02d%02dZ", $t[5] % 100, $t[4]+1, $t[3], $t[2], $t[1], $t[0])
: sprintf("%04d%02d%02d%02d%02d%02dZ", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]);
}
# RFC 3339 (e.g. "2024-01-15T10:30:00Z" or "2024-01-15T10:30:00.5+05:30")
if ($fmt eq 'rfc3339' || $val =~ /^\d{4}-/) {
$val =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|([+-])(\d{2}):(\d{2}))$/
or croak "FATAL: asn1_encode: invalid RFC 3339 time for $type: $val";
my ($YYYY,$MM,$DD,$hh,$mm,$ss,$fs,$tz,$sign,$oh,$om) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);
my $r;
if ($kind eq 'utc') {
croak "FATAL: asn1_encode: UTCTIME does not allow fractional seconds: $val"
if defined $fs && length $fs;
croak "FATAL: asn1_encode: UTCTIME year out of range: $YYYY (expected 1950..2049)"
if $YYYY < 1950 || $YYYY > 2049;
my $YY = ($YYYY >= 2000) ? $YYYY - 2000 : $YYYY - 1900;
$r = sprintf "%02d%02d%02d%02d%02d%02d", $YY, $MM, $DD, $hh, $mm, $ss;
}
else {
$r = sprintf "%04d%02d%02d%02d%02d%02d", $YYYY, $MM, $DD, $hh, $mm, $ss;
$r .= ".$fs" if defined $fs && $fs > 0;
}
$r .= ($tz eq 'Z') ? 'Z' : sprintf("%s%02d%02d", $sign, $oh, $om);
return $r;
}
croak "FATAL: asn1_encode: invalid $type value '$val' (expected RFC 3339 string or epoch)";
}
1;
=pod
=head1 NAME
Crypt::ASN1 - DER ASN.1 parser and encoder based on libtomcrypt
=head1 SYNOPSIS
use Crypt::ASN1 qw(asn1_decode_der asn1_encode_der asn1_to_string);
# --- decode ---
my $tree = asn1_decode_der($der_bytes);
my $tree = asn1_decode_der($der_bytes, { int => 'hex', bin => 'hex' });
# --- inspect ---
print asn1_to_string($tree);
# --- encode a decoded tree ---
my $der2 = asn1_encode_der($tree);
# --- build from scratch ---
my $der = asn1_encode_der([{
type => 'SEQUENCE',
value => [
{ type => 'INTEGER', value => '42' },
{ type => 'BOOLEAN', value => 1 },
{ type => 'OID', value => '1.2.840.113549.1.1.11' },
{ type => 'UTF8_STRING', value => 'hello' },
{ type => 'OCTET_STRING', value => "\x00\x01\x02" },
{ type => 'BIT_STRING', value => "\x03\x02\x01", bits => 20 },
{ type => 'NULL' },
{ type => 'UTCTIME', value => '2025-06-15T12:00:00Z' },
{ type => 'CUSTOM', class => 'CONTEXT_SPECIFIC',
constructed => 1, tag => 0,
value => [{ type => 'INTEGER', value => '2' }] },
],
}]);
=head1 DESCRIPTION
I<Since: CryptX-0.089>
Parses DER-encoded ASN.1 data into a Perl data structure without requiring
any schema, and encodes Perl data structures back to DER.
Uses libtomcrypt's C<der_decode_sequence_flexi> for decoding.
Both the decoder output and the encoder input use the same B<node hash>
structure described below. When given a tree produced by the decoder, the
encoder does its best to produce the same ASN.1 that was originally parsed,
regardless of what decode options were used.
=head1 EXPORT
Nothing is exported by default.
You can export selected functions:
use Crypt::ASN1 qw(asn1_decode_der asn1_encode_der);
Or all of them at once:
use Crypt::ASN1 ':all';
=head1 NODE HASH STRUCTURE
Both the decoder and encoder operate on the same data structure: an B<arrayref
of node hashrefs>. Each hashref represents one ASN.1 TLV (Tag-Length-Value)
element.
=head2 Common keys
Every node has three keys:
=over
=item C<type> (string, required)
The ASN.1 type name. Built-in values include:
BOOLEAN INTEGER NULL OID
OCTET_STRING BIT_STRING UTF8_STRING
PRINTABLE_STRING IA5_STRING TELETEX_STRING
UTCTIME GENERALIZEDTIME
SEQUENCE SET CUSTOM
The list above is not exhaustive for decoded input. If the decoder encounters
an ASN.1 tag that does not map to one of the built-in type names above, it is
returned as C<CUSTOM> with the appropriate C<class>, C<constructed>, and
C<tag> fields. This includes unsupported universal tags such as
C<ENUMERATED>, which decode as C<CUSTOM> with C<< class => "UNIVERSAL" >>.
=item C<value> (varies, required for most types)
The decoded value. Its Perl type depends on C<type> and sometimes on the
C<format> key -- see L</Per-type details> below.
=item C<format> (string, decoder sets it, encoder reads it)
Tells the encoder how the C<value> is represented so it can convert it back
to DER. Set automatically by the decoder; when building nodes from scratch
you may omit it -- the encoder then assumes the default representation for
each type.
=back
=head2 Per-type details
Each subsection below documents one C<type>. For types where the C<value>
representation depends on the decode option used, a B<format table> lists
every C<format>/C<value> combination. B<The encoder accepts every
combination shown> -- it reads C<format> and converts C<value> back to DER
automatically.
=head3 BOOLEAN
B<Keys>: C<type>, C<format>, C<value>.
C<value> is C<1> (true) or C<0> (false). C<format> is always C<"bool">.
{ type => "BOOLEAN", format => "bool", value => 1 }
=head3 INTEGER
B<Keys>: C<type>, C<format>, C<value>.
C<value> is an arbitrary-precision signed integer. C<format> describes the
representation:
format value decode option example
-------- --------------------------- ---------------- ---------------
decimal decimal string (default) "255"
hex lowercase hex string int => 'hex' "ff"
bytes big-endian binary string int => 'bytes' "\xff"
All three forms are accepted by the encoder. When C<format> is absent the
encoder treats C<value> as a decimal string (a Perl integer is fine too).
Negative integers: C<decimal> and C<hex> carry a leading C<-> (e.g. C<"-5">).
C<bytes> stores the unsigned magnitude only and is intended for naturally
unsigned values such as RSA moduli. When decoding with C<< int => 'bytes' >>,
negative ASN.1 INTEGER values are rejected.
=head3 NULL
B<Keys>: C<type>, C<format>, C<value>.
C<value> is always C<undef>. C<format> is always C<"null">. The encoder
ignores C<value>.
{ type => "NULL", format => "null", value => undef }
=head3 OID
B<Keys>: C<type>, C<format>, C<value>, and optionally C<name>.
lib/Crypt/ASN1.pm view on Meta::CPAN
When constructing nodes by hand you need C<type> and C<value> (plus the
extra keys noted above for C<CUSTOM> and C<BIT_STRING>). You may omit
C<format>; the encoder assumes:
Type default value interpretation
---------------- ------------------------------------------
INTEGER decimal string or Perl integer
OCTET_STRING raw bytes
BIT_STRING raw packed bytes, bits = length(value) * 8
UTCTIME RFC 3339 string (or all-digit epoch)
GENERALIZEDTIME RFC 3339 string (or all-digit epoch)
CUSTOM primitive raw bytes
You may also supply C<format> explicitly if you prefer to work with hex
or base64 representations:
# these two produce identical DER
{ type => "OCTET_STRING", value => "\x04\x01" }
{ type => "OCTET_STRING", format => "hex", value => "0401" }
=head1 FUNCTIONS
=head2 asn1_decode_der
my $tree = asn1_decode_der($der_bytes);
my $tree = asn1_decode_der($der_bytes, \%opts);
Parses C<$der_bytes> and returns an arrayref of top-level node hashrefs.
Croaks on parse error.
The optional C<%opts> hashref controls value formatting:
=over
=item C<int =E<gt> 'hex' | 'bytes'>
How to represent C<INTEGER> values. Default is a decimal string
(C<< format=>"decimal" >>).
C<'hex'> gives a lowercase hex string (C<< format=>"hex" >>).
C<'bytes'> gives a raw big-endian binary string (C<< format=>"bytes" >>) for
non-negative INTEGER values only; decoding croaks if the DER INTEGER is
negative.
=item C<bin =E<gt> 'hex' | 'base64'>
How to represent C<OCTET_STRING>, C<BIT_STRING>, and primitive C<CUSTOM>
values. Default is raw binary bytes (C<< format=>"bytes" >>).
C<'hex'> gives a lowercase hex string (C<< format=>"hex" >>).
C<'base64'> gives a Base64-encoded string (C<< format=>"base64" >>).
=item C<dt =E<gt> 'epoch'>
How to represent C<UTCTIME> and C<GENERALIZEDTIME> values. Default is an
RFC 3339 string (C<< format=>"rfc3339" >>).
C<'epoch'> gives a Unix timestamp integer (C<< format=>"epoch" >>).
This works reliably only on Perls with 64-bit integers; on 32-bit integer
Perls, large timestamps may overflow or lose precision.
=item C<oidmap =E<gt> \%map>
A hashref mapping dotted OID strings to friendly names. When a decoded
C<OID> node's value exists as a key in C<%map>, the node gets an additional
C<name> key with the mapped value. Does not affect encoding.
=back
=head2 asn1_decode_pem
my $tree = asn1_decode_pem($pem_string);
my $tree = asn1_decode_pem($pem_string, \%opts);
Decodes the PEM envelope first (via L<Crypt::Misc/pem_to_der>), then parses
the resulting DER bytes. Accepts the same C<%opts> as C<asn1_decode_der>.
=head2 asn1_decode_der_file
my $tree = asn1_decode_der_file($filename);
my $tree = asn1_decode_der_file($filename, \%opts);
Reads C<$filename> as raw binary and parses it as DER.
=head2 asn1_decode_pem_file
my $tree = asn1_decode_pem_file($filename);
my $tree = asn1_decode_pem_file($filename, \%opts);
Reads C<$filename>, decodes the PEM envelope, then parses the DER bytes.
=head2 asn1_encode_der
my $der_bytes = asn1_encode_der($tree);
Encodes C<$tree> (an arrayref of node hashrefs) to DER bytes. The input
may be a tree previously returned by C<asn1_decode_der> or one
built from scratch. Croaks on invalid input.
The encoder normalizes every node before encoding: it reads C<format> (if
present) to determine how to interpret C<value>, converts it to the canonical
DER form, and encodes it.
The current low-level encoder supports element content lengths up to
C<0xffffffff> bytes; larger values are rejected.
=head2 asn1_encode_pem
my $pem_string = asn1_encode_pem($tree, $header);
Encodes C<$tree> to DER, then wraps in a PEM envelope with the given
C<$header> (e.g. C<"CERTIFICATE">, C<"RSA PRIVATE KEY">). Defaults to
C<"DATA"> if C<$header> is omitted.
=head2 asn1_encode_der_file
asn1_encode_der_file($tree, $filename);
Encodes C<$tree> to DER and writes it to C<$filename>.
=head2 asn1_encode_pem_file
asn1_encode_pem_file($tree, $header, $filename);
Encodes C<$tree> to PEM and writes it to C<$filename>.
=head2 asn1_to_string
my $text = asn1_to_string($tree);
Returns a human-readable text representation of C<$tree> (an arrayref of
node hashrefs as returned by any C<asn1_decode_*> function). Useful for
debugging and inspection, similar to C<openssl asn1parse> output.
print asn1_to_string(asn1_decode_pem_file("cert.pem"));
produces output like:
SEQUENCE (3 elem)
SEQUENCE (8 elem)
context_specific [0] cons (1 elem)
INTEGER:2
INTEGER:17923815188543234454
SEQUENCE (2 elem)
OBJECT:1.2.840.113549.1.1.11
NULL:
...
BIT STRING:3082010a0282010100c242299a49420c21dcf9b957afcdc49... (2160 bit)
Binary values (C<OCTET_STRING>, C<BIT_STRING>, primitive C<CUSTOM>) are
shown as lowercase hex, truncated to 64 characters with C<...> for longer
values. C<BIT_STRING> additionally shows the bit count in parentheses.
C<OID> nodes that have a C<name> key (via C<oidmap>) show the name in
parentheses after the dotted value.
The function handles trees decoded with any combination of decode options
(C<int>, C<bin>, C<dt>).
=head1 SEE ALSO
L<CryptX>, L<Crypt::Misc>
=cut
( run in 0.581 second using v1.01-cache-2.11-cpan-fe3c2283af0 )