File Coverage

blib/lib/Crypt/ASN1.pm
Criterion Covered Total %
statement 230 239 96.2
branch 160 212 75.4
condition 53 70 75.7
subroutine 27 27 100.0
pod 8 8 100.0
total 478 556 85.9


line stmt bran cond sub pod time code
1             package Crypt::ASN1;
2              
3 2     2   89159 use strict;
  2         3  
  2         57  
4 2     2   10 use warnings;
  2         2  
  2         229  
5             our $VERSION = '0.089_001';
6              
7             require Exporter; our @ISA = qw(Exporter); ### use Exporter 5.57 'import';
8             our %EXPORT_TAGS = ( all => [qw(
9             asn1_decode_der asn1_decode_pem asn1_decode_der_file asn1_decode_pem_file
10             asn1_encode_der asn1_encode_pem asn1_encode_der_file asn1_encode_pem_file
11             asn1_to_string
12             )] );
13             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
14             our @EXPORT = qw();
15              
16 2     2   16 use Carp 'croak';
  2         2  
  2         80  
17 2     2   7 use Config ();
  2         9  
  2         36  
18 2     2   422 use CryptX;
  2         3  
  2         84  
19 2     2   777 use Crypt::Misc qw(pem_to_der der_to_pem read_rawfile write_rawfile decode_b64);
  2         5  
  2         6284  
20              
21             # --- decode (public) ---
22              
23             sub asn1_decode_pem {
24 4     4 1 10419 my $pem = shift;
25 4 50       18 my $der = pem_to_der($pem) or croak "FATAL: asn1_decode_pem: failed to decode PEM data";
26 4         467 return asn1_decode_der($der, @_);
27             }
28              
29             sub asn1_decode_der_file {
30 2     2 1 1404 my $file = shift;
31 2         10 return asn1_decode_der(read_rawfile($file), @_);
32             }
33              
34             sub asn1_decode_pem_file {
35 2     2 1 3218 my $file = shift;
36 2         8 return asn1_decode_pem(read_rawfile($file), @_);
37             }
38              
39             # --- encode (public) ---
40              
41             sub asn1_encode_der {
42 69     69 1 242201 my ($tree) = @_;
43 69 50       280 croak "FATAL: asn1_encode_der: argument must be an arrayref" unless ref $tree eq 'ARRAY';
44 69         207 my $normalized = [ map { _normalize_node($_) } @$tree ];
  69         172  
45 42         675 return _asn1_encode_der($normalized);
46             }
47              
48             sub asn1_encode_pem {
49 2     2 1 535 my ($tree, $header) = @_;
50 2 50       8 $header = 'DATA' unless defined $header;
51 2         8 my $der = asn1_encode_der($tree);
52 2         31 return der_to_pem($der, $header);
53             }
54              
55             sub asn1_encode_der_file {
56 1     1 1 605 my ($tree, $file) = @_;
57 1         4 my $der = asn1_encode_der($tree);
58 1         7 write_rawfile($file, $der);
59 1         4 return $der;
60             }
61              
62             sub asn1_encode_pem_file {
63 1     1 1 709 my ($tree, $header, $file) = @_;
64 1         4 my $pem = asn1_encode_pem($tree, $header);
65 1         5 write_rawfile($file, $pem);
66 1         4 return $pem;
67             }
68              
69             # --- dump (public) ---
70              
71             sub asn1_to_string {
72 4     4 1 8607 my ($tree) = @_;
73 4 50       18 croak "FATAL: asn1_to_string: argument must be an arrayref" unless ref $tree eq 'ARRAY';
74 4         7 my $out = '';
75 4         16 _dump_nodes(\$out, $tree, 0);
76 4         12 return $out;
77             }
78              
79             my %_LABEL = (
80             BOOLEAN => 'BOOLEAN', INTEGER => 'INTEGER', BIT_STRING => 'BIT STRING',
81             OCTET_STRING => 'OCTET STRING', NULL => 'NULL', OID => 'OBJECT',
82             UTF8_STRING => 'UTF8STRING', PRINTABLE_STRING => 'PRINTABLESTRING',
83             TELETEX_STRING => 'TELETEXSTRING', IA5_STRING => 'IA5STRING',
84             UTCTIME => 'UTCTIME', GENERALIZEDTIME => 'GENERALIZEDTIME',
85             SEQUENCE => 'SEQUENCE', SET => 'SET',
86             );
87              
88             sub _dump_nodes {
89 25     25   35 my ($out, $nodes, $depth) = @_;
90 25         32 for my $node (@$nodes) {
91 58         90 _dump_node($out, $node, $depth);
92             }
93             }
94              
95             sub _dump_node {
96 58     58   69 my ($out, $node, $depth) = @_;
97 58 50       98 my $type = defined $node->{type} ? $node->{type} : '?';
98 58         59 my $val = $node->{value};
99 58 50       78 my $fmt = defined $node->{format} ? $node->{format} : '';
100 58         70 my $indent = ' ' x $depth;
101              
102 58 100 100     119 if ($type eq 'SEQUENCE' || $type eq 'SET') {
103 18 50       25 my $n = ref $val eq 'ARRAY' ? scalar @$val : 0;
104 18         36 $$out .= "${indent}$_LABEL{$type} ($n elem)\n";
105 18 50       36 _dump_nodes($out, $val, $depth + 1) if $n;
106 18         26 return;
107             }
108              
109 40 100       94 if ($type eq 'CUSTOM') {
110 3 50       9 my $cls = defined $node->{class} ? $node->{class} : 'CONTEXT_SPECIFIC';
111 3 50       33 my $tag = defined $node->{tag} ? $node->{tag} : 0;
112 3         6 my $cons = $node->{constructed};
113 3         8 my $label = lc($cls) . " [$tag]";
114 3 50 33     14 if ($cons && ref $val eq 'ARRAY') {
115 3         5 my $n = scalar @$val;
116 3         7 $$out .= "${indent}$label cons ($n elem)\n";
117 3         8 _dump_nodes($out, $val, $depth + 1);
118             } else {
119 0         0 $$out .= "${indent}$label prim:${\ _dump_value_short($val, $fmt)}\n";
  0         0  
120             }
121 3         7 return;
122             }
123              
124 37 50       61 my $label = exists $_LABEL{$type} ? $_LABEL{$type} : $type;
125 37         43 $$out .= "${indent}${label}:${\ _dump_value($type, $val, $fmt, $node)}\n";
  37         70  
126             }
127              
128             sub _dump_value {
129 37     37   53 my ($type, $val, $fmt, $node) = @_;
130              
131 37 100       58 return '' if $type eq 'NULL';
132              
133 33 100       41 if ($type eq 'BOOLEAN') {
134 2 100       13 return $val ? 'TRUE' : 'FALSE';
135             }
136 31 100       53 if ($type eq 'INTEGER') {
137 4 50       8 return _dump_value_short($val, $fmt) if $fmt eq 'bytes';
138 4 50       11 my $s = defined $val ? "$val" : '';
139 4 50       24 return length($s) > 64 ? substr($s, 0, 61) . '...' : $s;
140             }
141 27 100       36 if ($type eq 'OID') {
142 10 50       15 my $s = defined $val ? $val : '';
143 10 100       22 $s .= " ($node->{name})" if defined $node->{name};
144 10         24 return $s;
145             }
146 17 100 100     65 if ($type eq 'UTF8_STRING' || $type eq 'PRINTABLE_STRING'
      100        
      100        
147             || $type eq 'IA5_STRING' || $type eq 'TELETEX_STRING') {
148 6 50       9 my $s = defined $val ? $val : '';
149 6 50       36 return length($s) > 64 ? substr($s, 0, 61) . '...' : $s;
150             }
151 11 100 100     22 if ($type eq 'UTCTIME' || $type eq 'GENERALIZEDTIME') {
152 4 50       13 return defined $val ? "$val" : '';
153             }
154 7 50 66     15 if ($type eq 'OCTET_STRING' || $type eq 'BIT_STRING') {
155 7         7 my $extra = '';
156 7 100 66     15 if ($type eq 'BIT_STRING' && defined $node->{bits}) {
157 3         4 $extra = " ($node->{bits} bit)";
158             }
159 7         12 return _dump_value_short($val, $fmt) . $extra;
160             }
161 0 0       0 return defined $val ? "$val" : '';
162             }
163              
164             sub _dump_value_short {
165 7     7   11 my ($val, $fmt) = @_;
166 7 50 33     18 return '' unless defined $val && length $val;
167 7         6 my $hex;
168 7 50       12 if ($fmt eq 'hex') {
    100          
169 0         0 $hex = lc($val);
170             } elsif ($fmt eq 'base64') {
171 5         24 $hex = lc unpack("H*", decode_b64($val));
172             } else {
173 2         8 $hex = lc unpack("H*", $val);
174             }
175 7 100       30 return length($hex) > 64 ? substr($hex, 0, 61) . '...' : $hex;
176             }
177              
178             # --- normalization (internal) ---
179              
180             my %_CLASS_MAP = (UNIVERSAL => 0, APPLICATION => 1, CONTEXT_SPECIFIC => 2, PRIVATE => 3);
181             my $_MAX_ULONG_DEC = _max_ulong_dec();
182             my $_MAX_OID_SECOND_ARC_DEC = _max_oid_second_arc_dec();
183              
184             sub _normalize_node {
185 221     221   267 my ($node) = @_;
186 221 50       393 croak "FATAL: asn1_encode: node must be a hashref" unless ref $node eq 'HASH';
187 221 50       423 my $type = defined $node->{type} ? $node->{type} : croak "FATAL: asn1_encode: node missing 'type'";
188 221 100       383 my $fmt = defined $node->{format} ? $node->{format} : '';
189 221         293 my $val = $node->{value};
190              
191             # --- constructed types ---
192 221 100 100     653 if ($type eq 'SEQUENCE' || $type eq 'SET') {
193 63 50       93 croak "FATAL: asn1_encode: $type value must be arrayref" unless ref($val) eq 'ARRAY';
194 63         88 return { type => $type, value => [ map { _normalize_node($_) } @$val ] };
  143         179  
195             }
196             # --- INTEGER: always normalize to decimal string ---
197 158 100       278 if ($type eq 'INTEGER') {
198 25 50       94 croak "FATAL: asn1_encode: INTEGER missing value" unless defined $val;
199 25 100       66 if ($fmt eq 'hex') {
    100          
200 5         38 require Math::BigInt;
201 5         9 my $hex = $val;
202 5         16 my $neg = ($hex =~ s/^-//);
203 5         37 my $bi = Math::BigInt->new("0x$hex");
204 5 100       1445 $bi->bneg() if $neg;
205 5         157 $val = $bi->bstr();
206             }
207             elsif ($fmt eq 'bytes') {
208 3         2627 require Math::BigInt;
209 3         55381 $val = Math::BigInt->new("0x" . unpack("H*", $val))->bstr();
210             }
211             else {
212 17         28 $val = "$val"; # stringify Perl number
213             }
214 25         22510 return { type => 'INTEGER', value => $val };
215             }
216             # --- BOOLEAN ---
217 133 100       202 if ($type eq 'BOOLEAN') {
218 4 100       26 return { type => 'BOOLEAN', value => $val ? 1 : 0 };
219             }
220             # --- NULL ---
221 129 100       193 if ($type eq 'NULL') {
222 11         58 return { type => 'NULL' };
223             }
224             # --- OID ---
225 118 100       180 if ($type eq 'OID') {
226 32 50       43 croak "FATAL: asn1_encode: OID missing value" unless defined $val;
227 32         50 return { type => 'OID', value => _normalize_oid($val) };
228             }
229             # --- OCTET_STRING: always normalize to raw bytes ---
230 86 100       138 if ($type eq 'OCTET_STRING') {
231 21         33 $val = _bin_to_raw($val, $fmt);
232 17         54 return { type => 'OCTET_STRING', value => $val };
233             }
234             # --- BIT_STRING: raw bytes + bits ---
235 65 100       147 if ($type eq 'BIT_STRING') {
236 14         23 $val = _bin_to_raw($val, $fmt);
237             my $bits = exists $node->{bits}
238 14 100       45 ? _normalize_bit_count($node->{bits}, length($val))
239             : (length($val) * 8);
240 11         51 return { type => 'BIT_STRING', value => $val, bits => $bits };
241             }
242             # --- string types ---
243 51 100       81 if ($type eq 'UTF8_STRING') {
244 8 50       51 return { type => 'UTF8_STRING', value => defined $val ? $val : '' };
245             }
246 43 100 100     185 if ($type eq 'IA5_STRING' || $type eq 'PRINTABLE_STRING' || $type eq 'TELETEX_STRING') {
      100        
247 5 50       28 return { type => $type, value => defined $val ? $val : '' };
248             }
249             # --- time types ---
250 38 100       72 if ($type eq 'UTCTIME') {
251 16         39 return { type => 'UTCTIME', value => _normalize_time($val, $fmt, 'utc') };
252             }
253 22 100       47 if ($type eq 'GENERALIZEDTIME') {
254 3         10 return { type => 'GENERALIZEDTIME', value => _normalize_time($val, $fmt, 'gen') };
255             }
256             # --- CUSTOM ---
257 19 50       41 if ($type eq 'CUSTOM') {
258 19 50       42 my $class_name = defined $node->{class} ? $node->{class} : 'CONTEXT_SPECIFIC';
259             croak "FATAL: asn1_encode: invalid CUSTOM class '$class_name'"
260 19 100       369 unless exists $_CLASS_MAP{$class_name};
261 17         28 my $cls = $_CLASS_MAP{$class_name};
262 17 100       34 my $constr = $node->{constructed} ? 1 : 0;
263 17 50       51 my $tag = _normalize_custom_tag(defined $node->{tag} ? $node->{tag} : 0);
264 13         58 my %n = (type => 'CUSTOM', class => $cls, constructed => $constr, tag => $tag);
265 13 100       27 if ($constr) {
266 11 100       155 croak "FATAL: asn1_encode: CUSTOM constructed value must be arrayref"
267             unless ref($val) eq 'ARRAY';
268 10         19 $n{value} = [ map { _normalize_node($_) } @$val ];
  9         16  
269             }
270             else {
271 2 100       144 croak "FATAL: asn1_encode: CUSTOM primitive value must not be a reference"
272             if ref($val);
273 1         6 $n{value} = _bin_to_raw($val, $fmt);
274             }
275 11         47 return \%n;
276             }
277 0         0 croak "FATAL: asn1_encode: unsupported type '$type'";
278             }
279              
280             # Convert binary value from hex/base64 to raw bytes
281             sub _bin_to_raw {
282 36     36   58 my ($val, $fmt) = @_;
283 36 50       65 $val = '' unless defined $val;
284 36 50       55 $fmt = '' unless defined $fmt;
285 36 100       56 if ($fmt eq 'hex') {
286 8 100       406 croak "FATAL: asn1_encode: invalid hex value"
287             unless $val =~ /\A(?:[0-9A-Fa-f]{2})*\z/;
288 6         26 return pack("H*", $val);
289             }
290 28 100       53 if ($fmt eq 'base64') {
291 8 100       324 croak "FATAL: asn1_encode: invalid base64 value"
292             unless $val =~ /\A(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?\z/;
293 6         38 return decode_b64($val);
294             }
295 20         33 return $val;
296             }
297              
298             sub _normalize_oid {
299 32     32   44 my ($val) = @_;
300 32         54 my $oid = "$val";
301              
302 32 100       495 croak "FATAL: asn1_encode: invalid OID value '$oid'"
303             unless $oid =~ /\A\d+(?:\.\d+)+\z/;
304              
305 30         77 my @arc = split /\./, $oid;
306 30 50       49 croak "FATAL: asn1_encode: OID must have at least 2 arcs"
307             unless @arc >= 2;
308 30 50 33     83 croak "FATAL: asn1_encode: OID first arc must be 0, 1, or 2"
309             unless $arc[0] >= 0 && $arc[0] <= 2;
310 30 50 66     59 croak "FATAL: asn1_encode: OID second arc must be between 0 and 39 when first arc is 0 or 1"
311             if $arc[0] < 2 && $arc[1] > 39;
312 30 50       40 croak "FATAL: asn1_encode: OID has too many arcs (maximum 64)"
313             if @arc > 64;
314              
315 30         55 for my $i (1 .. $#arc) {
316 112 100 100     180 my $limit = ($i == 1 && $arc[0] == 2) ? $_MAX_OID_SECOND_ARC_DEC : $_MAX_ULONG_DEC;
317 112 100       123 croak "FATAL: asn1_encode: OID arc '$arc[$i]' is too large for this encoder"
318             unless _decimal_fits_limit($arc[$i], $limit);
319             }
320              
321 28         84 return $oid;
322             }
323              
324             sub _normalize_bit_count {
325 13     13   20 my ($bits, $byte_len) = @_;
326 13         18 my $max_bits = $byte_len * 8;
327              
328 13 50       22 croak "FATAL: asn1_encode: BIT_STRING bits missing"
329             unless defined $bits;
330              
331 13         23 my $raw = "$bits";
332 13 100       322 croak "FATAL: asn1_encode: BIT_STRING bits must be a non-negative integer"
333             unless $raw =~ /\A\d+\z/;
334              
335 11         17 $bits = int($raw);
336 11 100       255 croak "FATAL: asn1_encode: BIT_STRING bits exceeds available data ($bits > $max_bits)"
337             if $bits > $max_bits;
338              
339 10         14 return $bits;
340             }
341              
342             sub _normalize_custom_tag {
343 17     17   33 my ($tag) = @_;
344 17         33 my $raw = "$tag";
345              
346 17 100       523 croak "FATAL: asn1_encode: CUSTOM tag must be a non-negative integer"
347             unless $raw =~ /\A\d+\z/;
348 14 100       28 croak "FATAL: asn1_encode: CUSTOM tag '$raw' is too large for this encoder"
349             unless _decimal_fits_limit($raw, $_MAX_ULONG_DEC);
350              
351 13         47 return int($raw);
352             }
353              
354             sub _decimal_fits_limit {
355 126     126   152 my ($value, $limit) = @_;
356 126         116 $value = "$value";
357 126         142 $value =~ s/\A0+(?=\d)//;
358 126 100       256 return 1 if length($value) < length($limit);
359 3 100       155 return 0 if length($value) > length($limit);
360 2         286 return $value le $limit;
361             }
362              
363             sub _max_ulong_dec {
364 2   50 2   190 my $bytes = $Config::Config{longsize} || 0;
365 2 50       10 return '4294967295' if $bytes == 4;
366 2 50       8 return '18446744073709551615' if $bytes == 8;
367              
368 0         0 require Math::BigInt;
369 0         0 return Math::BigInt->new(2)->bpow($bytes * 8)->bsub(1)->bstr();
370             }
371              
372             sub _max_oid_second_arc_dec {
373 2   50 2   10 my $bytes = $Config::Config{longsize} || 0;
374 2 50       6 return '4294967215' if $bytes == 4;
375 2 50       5 return '18446744073709551535' if $bytes == 8;
376              
377 0         0 require Math::BigInt;
378 0         0 return Math::BigInt->new(_max_ulong_dec())->bsub(80)->bstr();
379             }
380              
381             # Normalize timestamp to ASN.1 wire format
382             sub _normalize_time {
383 19     19   58 my ($val, $fmt, $kind) = @_;
384 19 50       40 croak "FATAL: asn1_encode: time value missing" unless defined $val;
385 19 100       41 my $type = $kind eq 'utc' ? 'UTCTIME' : 'GENERALIZEDTIME';
386              
387             # epoch (all digits, possibly negative)
388 19 100 66     141 if ($fmt eq 'epoch' || ($fmt eq '' && $val =~ /^-?\d+$/)) {
      66        
389 5         21 my @t = gmtime($val);
390 5         13 my $year = $t[5] + 1900;
391 5 50       12 if ($kind eq 'utc') {
392 5 100 66     182 croak "FATAL: asn1_encode: UTCTIME year out of range: $year (expected 1950..2049)"
393             if $year < 1950 || $year > 2049;
394             }
395 4 50       43 return ($kind eq 'utc')
396             ? sprintf("%02d%02d%02d%02d%02d%02dZ", $t[5] % 100, $t[4]+1, $t[3], $t[2], $t[1], $t[0])
397             : sprintf("%04d%02d%02d%02d%02d%02dZ", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]);
398             }
399              
400             # RFC 3339 (e.g. "2024-01-15T10:30:00Z" or "2024-01-15T10:30:00.5+05:30")
401 14 100 100     64 if ($fmt eq 'rfc3339' || $val =~ /^\d{4}-/) {
402 10 50       54 $val =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|([+-])(\d{2}):(\d{2}))$/
403             or croak "FATAL: asn1_encode: invalid RFC 3339 time for $type: $val";
404 10         69 my ($YYYY,$MM,$DD,$hh,$mm,$ss,$fs,$tz,$sign,$oh,$om) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);
405 10         14 my $r;
406 10 100       21 if ($kind eq 'utc') {
407 9 100 66     166 croak "FATAL: asn1_encode: UTCTIME does not allow fractional seconds: $val"
408             if defined $fs && length $fs;
409 8 100 100     381 croak "FATAL: asn1_encode: UTCTIME year out of range: $YYYY (expected 1950..2049)"
410             if $YYYY < 1950 || $YYYY > 2049;
411 6 50       11 my $YY = ($YYYY >= 2000) ? $YYYY - 2000 : $YYYY - 1900;
412 6         25 $r = sprintf "%02d%02d%02d%02d%02d%02d", $YY, $MM, $DD, $hh, $mm, $ss;
413             }
414             else {
415 1         9 $r = sprintf "%04d%02d%02d%02d%02d%02d", $YYYY, $MM, $DD, $hh, $mm, $ss;
416 1 50 33     10 $r .= ".$fs" if defined $fs && $fs > 0;
417             }
418 7 50       19 $r .= ($tz eq 'Z') ? 'Z' : sprintf("%s%02d%02d", $sign, $oh, $om);
419 7         47 return $r;
420             }
421              
422 4         569 croak "FATAL: asn1_encode: invalid $type value '$val' (expected RFC 3339 string or epoch)";
423             }
424              
425             1;
426              
427             =pod
428              
429             =head1 NAME
430              
431             Crypt::ASN1 - DER ASN.1 parser and encoder based on libtomcrypt
432              
433             =head1 SYNOPSIS
434              
435             use Crypt::ASN1 qw(asn1_decode_der asn1_encode_der asn1_to_string);
436              
437             # --- decode ---
438             my $tree = asn1_decode_der($der_bytes);
439             my $tree = asn1_decode_der($der_bytes, { int => 'hex', bin => 'hex' });
440              
441             # --- inspect ---
442             print asn1_to_string($tree);
443              
444             # --- encode a decoded tree ---
445             my $der2 = asn1_encode_der($tree);
446              
447             # --- build from scratch ---
448             my $der = asn1_encode_der([{
449             type => 'SEQUENCE',
450             value => [
451             { type => 'INTEGER', value => '42' },
452             { type => 'BOOLEAN', value => 1 },
453             { type => 'OID', value => '1.2.840.113549.1.1.11' },
454             { type => 'UTF8_STRING', value => 'hello' },
455             { type => 'OCTET_STRING', value => "\x00\x01\x02" },
456             { type => 'BIT_STRING', value => "\x03\x02\x01", bits => 20 },
457             { type => 'NULL' },
458             { type => 'UTCTIME', value => '2025-06-15T12:00:00Z' },
459             { type => 'CUSTOM', class => 'CONTEXT_SPECIFIC',
460             constructed => 1, tag => 0,
461             value => [{ type => 'INTEGER', value => '2' }] },
462             ],
463             }]);
464              
465             =head1 DESCRIPTION
466              
467             I
468              
469             Parses DER-encoded ASN.1 data into a Perl data structure without requiring
470             any schema, and encodes Perl data structures back to DER.
471             Uses libtomcrypt's C for decoding.
472              
473             Both the decoder output and the encoder input use the same B
474             structure described below. When given a tree produced by the decoder, the
475             encoder does its best to produce the same ASN.1 that was originally parsed,
476             regardless of what decode options were used.
477              
478             =head1 EXPORT
479              
480             Nothing is exported by default.
481              
482             You can export selected functions:
483              
484             use Crypt::ASN1 qw(asn1_decode_der asn1_encode_der);
485              
486             Or all of them at once:
487              
488             use Crypt::ASN1 ':all';
489              
490             =head1 NODE HASH STRUCTURE
491              
492             Both the decoder and encoder operate on the same data structure: an B
493             of node hashrefs>. Each hashref represents one ASN.1 TLV (Tag-Length-Value)
494             element.
495              
496             =head2 Common keys
497              
498             Every node has three keys:
499              
500             =over
501              
502             =item C (string, required)
503              
504             The ASN.1 type name. Built-in values include:
505              
506             BOOLEAN INTEGER NULL OID
507             OCTET_STRING BIT_STRING UTF8_STRING
508             PRINTABLE_STRING IA5_STRING TELETEX_STRING
509             UTCTIME GENERALIZEDTIME
510             SEQUENCE SET CUSTOM
511              
512             The list above is not exhaustive for decoded input. If the decoder encounters
513             an ASN.1 tag that does not map to one of the built-in type names above, it is
514             returned as C with the appropriate C, C, and
515             C fields. This includes unsupported universal tags such as
516             C, which decode as C with C<< class => "UNIVERSAL" >>.
517              
518             =item C (varies, required for most types)
519              
520             The decoded value. Its Perl type depends on C and sometimes on the
521             C key -- see L below.
522              
523             =item C (string, decoder sets it, encoder reads it)
524              
525             Tells the encoder how the C is represented so it can convert it back
526             to DER. Set automatically by the decoder; when building nodes from scratch
527             you may omit it -- the encoder then assumes the default representation for
528             each type.
529              
530             =back
531              
532             =head2 Per-type details
533              
534             Each subsection below documents one C. For types where the C
535             representation depends on the decode option used, a B lists
536             every C/C combination. B
537             combination shown> -- it reads C and converts C back to DER
538             automatically.
539              
540             =head3 BOOLEAN
541              
542             B: C, C, C.
543              
544             C is C<1> (true) or C<0> (false). C is always C<"bool">.
545              
546             { type => "BOOLEAN", format => "bool", value => 1 }
547              
548             =head3 INTEGER
549              
550             B: C, C, C.
551              
552             C is an arbitrary-precision signed integer. C describes the
553             representation:
554              
555             format value decode option example
556             -------- --------------------------- ---------------- ---------------
557             decimal decimal string (default) "255"
558             hex lowercase hex string int => 'hex' "ff"
559             bytes big-endian binary string int => 'bytes' "\xff"
560              
561             All three forms are accepted by the encoder. When C is absent the
562             encoder treats C as a decimal string (a Perl integer is fine too).
563              
564             Negative integers: C and C carry a leading C<-> (e.g. C<"-5">).
565             C stores the unsigned magnitude only and is intended for naturally
566             unsigned values such as RSA moduli. When decoding with C<< int => 'bytes' >>,
567             negative ASN.1 INTEGER values are rejected.
568              
569             =head3 NULL
570              
571             B: C, C, C.
572              
573             C is always C. C is always C<"null">. The encoder
574             ignores C.
575              
576             { type => "NULL", format => "null", value => undef }
577              
578             =head3 OID
579              
580             B: C, C, C, and optionally C.
581              
582             C is a dotted-decimal OID string (at least two arcs). C is
583             always C<"oid">.
584              
585             { type => "OID", format => "oid", value => "1.2.840.113549.1.1.11" }
586              
587             As a convenience, the encoder accepts textual arcs with leading zeros and
588             lets DER encoding canonicalize them. For example, C<"2.000.1"> encodes and
589             decodes back as C<"2.0.1">.
590              
591             B: C -- present only when the C decode option
592             is supplied and the OID is found in the map. Ignored by the encoder.
593              
594             { ..., name => "sha256WithRSAEncryption" } # when oidmap matches
595              
596             =head3 OCTET_STRING
597              
598             B: C, C, C.
599              
600             C is binary data. C describes the representation:
601              
602             format value decode option example
603             -------- --------------------------- ----------------- --------
604             bytes raw binary string (default) "\x04\x01"
605             hex lowercase hex string bin => 'hex' "0401"
606             base64 Base64-encoded string bin => 'base64' "BAE="
607              
608             All three forms are accepted by the encoder. When C is absent the
609             encoder treats C as raw bytes.
610              
611             =head3 BIT_STRING
612              
613             B: C, C, C, C.
614              
615             C is the packed bit data (MSB-first). C follows the same
616             rules as C (C<"bytes">, C<"hex">, or C<"base64">). All three
617             forms are accepted by the encoder.
618              
619             C is the exact number of significant bits. The quantity
620             C<< 8 * byte_length(value) - bits >> gives the number of unused trailing
621             bits in the last byte.
622              
623             When C is absent the encoder treats C as raw bytes. When
624             C is absent it defaults to C<< 8 * length(value) >> (no unused bits).
625              
626             # default format (raw bytes, 25 significant bits)
627             { type => "BIT_STRING", format => "bytes",
628             value => "\x03\x02\x01\x00", bits => 25 }
629              
630             # hex format
631             { type => "BIT_STRING", format => "hex",
632             value => "03020100", bits => 25 }
633              
634             =head3 UTF8_STRING
635              
636             B: C, C, C.
637              
638             C is a Perl Unicode string (C flag on). C is always
639             C<"utf8">.
640              
641             { type => "UTF8_STRING", format => "utf8", value => "caf\x{e9}" }
642              
643             =head3 PRINTABLE_STRING, IA5_STRING, TELETEX_STRING
644              
645             B: C, C, C.
646              
647             C is a byte string. C is always C<"string"> for all three.
648              
649             { type => "PRINTABLE_STRING", format => "string", value => "abc" }
650             { type => "IA5_STRING", format => "string", value => "ia5" }
651             { type => "TELETEX_STRING", format => "string", value => "tele" }
652              
653             =head3 UTCTIME
654              
655             B: C, C, C.
656              
657             C is a timestamp. C describes the representation:
658              
659             format value decode option example
660             -------- ----------------------------- --------------- -----------------------
661             rfc3339 RFC 3339 string (default) "2024-01-15T10:30:00Z"
662             epoch Unix timestamp (integer) dt => 'epoch' 1705314600
663              
664             Both forms are accepted by the encoder. When C is absent, the
665             encoder auto-detects: an all-digit value is treated as epoch, a value
666             matching C is treated as RFC 3339.
667              
668             For C, encoder input must fall within the UTCTime year window
669             C<1950..2049>; values outside that range are rejected. Fractional seconds
670             are also rejected for C.
671              
672             Time validation in the encoder is currently B, not full calendar
673             validation. The encoder checks the accepted input shape and ASN.1-specific
674             constraints above, but it does not verify that every RFC 3339-looking date
675             and time is semantically valid.
676              
677             The decoder expands the 2-digit UTCTime year using the RFC 5280 window
678             (YY E= 50 E 19YY, else 20YY). Timezone offsets are preserved
679             (e.g. C<"2024-01-15T10:30:00+05:30">).
680              
681             =head3 GENERALIZEDTIME
682              
683             B: C, C, C.
684              
685             Same C rules as C; both forms are accepted by the encoder.
686             Fractional seconds are preserved (e.g. C<"2024-01-15T10:30:00.125Z">).
687             Validation is likewise syntactic only; semantically invalid calendar values
688             that match the accepted timestamp syntax are not currently rejected.
689              
690             =head3 SEQUENCE
691              
692             B: C, C, C.
693              
694             C is an arrayref of child node hashrefs (in order). C is
695             always C<"array">.
696              
697             { type => "SEQUENCE", format => "array", value => [ ...children... ] }
698              
699             =head3 SET
700              
701             B: C, C, C.
702              
703             Same structure as C. C is always C<"array">. Both
704             ASN.1 SET and SET OF are represented as C "SET"> (they share
705             the same DER tag C<0x31>).
706              
707             =head3 CUSTOM
708              
709             Represents any tag that does not map to one of the built-in type names above.
710             This is commonly used for context-specific implicit/explicit tags (C<[0]>,
711             C<[1]>, ...) found in X.509 certificates and other ASN.1 schemas, but it can
712             also be emitted by the decoder for unsupported universal tags.
713              
714             B: C, C, C, C, C, C.
715              
716             =over
717              
718             =item C (string) -- C<"CONTEXT_SPECIFIC">, C<"APPLICATION">, C<"UNIVERSAL">, or C<"PRIVATE">
719              
720             =item C (integer) -- C<1> if constructed, C<0> if primitive
721              
722             =item C (integer) -- the tag number (e.g. C<0> for C<[0]>)
723              
724             Must be a non-negative integer within the range supported by the current
725             encoder build.
726              
727             =back
728              
729             B (C<< constructed => 1 >>): C is an arrayref of child
730             nodes. C is C<"array">.
731              
732             { type => "CUSTOM", format => "array",
733             class => "CONTEXT_SPECIFIC", constructed => 1, tag => 0,
734             value => [ { type => "INTEGER", ... } ] }
735              
736             B (C<< constructed => 0 >>): C is raw data. C
737             follows the same rules as C (C<"bytes">, C<"hex">, or
738             C<"base64"> depending on the C decode option). All three forms are
739             accepted by the encoder. Primitive C values must not be references.
740              
741             # default format
742             { type => "CUSTOM", format => "bytes",
743             class => "CONTEXT_SPECIFIC", constructed => 0, tag => 1,
744             value => "\xAA\xBB" }
745              
746             # hex format (bin => 'hex')
747             { type => "CUSTOM", format => "hex",
748             class => "CONTEXT_SPECIFIC", constructed => 0, tag => 1,
749             value => "aabb" }
750              
751             =head2 Re-encoding Decoded Trees
752              
753             The encoder reads C and converts C back to DER before
754             encoding. When given a tree returned by C, it does its best
755             to produce the same ASN.1 that was originally parsed, regardless of the
756             decode options used:
757              
758             my $tree = asn1_decode_der($der, { int=>'hex', bin=>'base64', dt=>'epoch' });
759             my $der2 = asn1_encode_der($tree);
760              
761             =head2 Building nodes from scratch
762              
763             When constructing nodes by hand you need C and C (plus the
764             extra keys noted above for C and C). You may omit
765             C; the encoder assumes:
766              
767             Type default value interpretation
768             ---------------- ------------------------------------------
769             INTEGER decimal string or Perl integer
770             OCTET_STRING raw bytes
771             BIT_STRING raw packed bytes, bits = length(value) * 8
772             UTCTIME RFC 3339 string (or all-digit epoch)
773             GENERALIZEDTIME RFC 3339 string (or all-digit epoch)
774             CUSTOM primitive raw bytes
775              
776             You may also supply C explicitly if you prefer to work with hex
777             or base64 representations:
778              
779             # these two produce identical DER
780             { type => "OCTET_STRING", value => "\x04\x01" }
781             { type => "OCTET_STRING", format => "hex", value => "0401" }
782              
783             =head1 FUNCTIONS
784              
785             =head2 asn1_decode_der
786              
787             my $tree = asn1_decode_der($der_bytes);
788             my $tree = asn1_decode_der($der_bytes, \%opts);
789              
790             Parses C<$der_bytes> and returns an arrayref of top-level node hashrefs.
791             Croaks on parse error.
792              
793             The optional C<%opts> hashref controls value formatting:
794              
795             =over
796              
797             =item C 'hex' | 'bytes'>
798              
799             How to represent C values. Default is a decimal string
800             (C<< format=>"decimal" >>).
801             C<'hex'> gives a lowercase hex string (C<< format=>"hex" >>).
802             C<'bytes'> gives a raw big-endian binary string (C<< format=>"bytes" >>) for
803             non-negative INTEGER values only; decoding croaks if the DER INTEGER is
804             negative.
805              
806             =item C 'hex' | 'base64'>
807              
808             How to represent C, C, and primitive C
809             values. Default is raw binary bytes (C<< format=>"bytes" >>).
810             C<'hex'> gives a lowercase hex string (C<< format=>"hex" >>).
811             C<'base64'> gives a Base64-encoded string (C<< format=>"base64" >>).
812              
813             =item C
'epoch'>
814              
815             How to represent C and C values. Default is an
816             RFC 3339 string (C<< format=>"rfc3339" >>).
817             C<'epoch'> gives a Unix timestamp integer (C<< format=>"epoch" >>).
818             This works reliably only on Perls with 64-bit integers; on 32-bit integer
819             Perls, large timestamps may overflow or lose precision.
820              
821             =item C \%map>
822              
823             A hashref mapping dotted OID strings to friendly names. When a decoded
824             C node's value exists as a key in C<%map>, the node gets an additional
825             C key with the mapped value. Does not affect encoding.
826              
827             =back
828              
829             =head2 asn1_decode_pem
830              
831             my $tree = asn1_decode_pem($pem_string);
832             my $tree = asn1_decode_pem($pem_string, \%opts);
833              
834             Decodes the PEM envelope first (via L), then parses
835             the resulting DER bytes. Accepts the same C<%opts> as C.
836              
837             =head2 asn1_decode_der_file
838              
839             my $tree = asn1_decode_der_file($filename);
840             my $tree = asn1_decode_der_file($filename, \%opts);
841              
842             Reads C<$filename> as raw binary and parses it as DER.
843              
844             =head2 asn1_decode_pem_file
845              
846             my $tree = asn1_decode_pem_file($filename);
847             my $tree = asn1_decode_pem_file($filename, \%opts);
848              
849             Reads C<$filename>, decodes the PEM envelope, then parses the DER bytes.
850              
851             =head2 asn1_encode_der
852              
853             my $der_bytes = asn1_encode_der($tree);
854              
855             Encodes C<$tree> (an arrayref of node hashrefs) to DER bytes. The input
856             may be a tree previously returned by C or one
857             built from scratch. Croaks on invalid input.
858              
859             The encoder normalizes every node before encoding: it reads C (if
860             present) to determine how to interpret C, converts it to the canonical
861             DER form, and encodes it.
862              
863             The current low-level encoder supports element content lengths up to
864             C<0xffffffff> bytes; larger values are rejected.
865              
866             =head2 asn1_encode_pem
867              
868             my $pem_string = asn1_encode_pem($tree, $header);
869              
870             Encodes C<$tree> to DER, then wraps in a PEM envelope with the given
871             C<$header> (e.g. C<"CERTIFICATE">, C<"RSA PRIVATE KEY">). Defaults to
872             C<"DATA"> if C<$header> is omitted.
873              
874             =head2 asn1_encode_der_file
875              
876             asn1_encode_der_file($tree, $filename);
877              
878             Encodes C<$tree> to DER and writes it to C<$filename>.
879              
880             =head2 asn1_encode_pem_file
881              
882             asn1_encode_pem_file($tree, $header, $filename);
883              
884             Encodes C<$tree> to PEM and writes it to C<$filename>.
885              
886             =head2 asn1_to_string
887              
888             my $text = asn1_to_string($tree);
889              
890             Returns a human-readable text representation of C<$tree> (an arrayref of
891             node hashrefs as returned by any C function). Useful for
892             debugging and inspection, similar to C output.
893              
894             print asn1_to_string(asn1_decode_pem_file("cert.pem"));
895              
896             produces output like:
897              
898             SEQUENCE (3 elem)
899             SEQUENCE (8 elem)
900             context_specific [0] cons (1 elem)
901             INTEGER:2
902             INTEGER:17923815188543234454
903             SEQUENCE (2 elem)
904             OBJECT:1.2.840.113549.1.1.11
905             NULL:
906             ...
907             BIT STRING:3082010a0282010100c242299a49420c21dcf9b957afcdc49... (2160 bit)
908              
909             Binary values (C, C, primitive C) are
910             shown as lowercase hex, truncated to 64 characters with C<...> for longer
911             values. C additionally shows the bit count in parentheses.
912             C nodes that have a C key (via C) show the name in
913             parentheses after the dotted value.
914              
915             The function handles trees decoded with any combination of decode options
916             (C, C, C
).
917              
918             =head1 SEE ALSO
919              
920             L, L
921              
922             =cut