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   132648 use strict;
  2         5  
  2         122  
4 2     2   26 use warnings;
  2         4  
  2         389  
5             our $VERSION = '0.089';
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         5  
  2         174  
17 2     2   15 use Config ();
  2         6  
  2         66  
18 2     2   1017 use CryptX;
  2         9  
  2         99  
19 2     2   1249 use Crypt::Misc qw(pem_to_der der_to_pem read_rawfile write_rawfile decode_b64);
  2         7  
  2         12429  
20              
21             # --- decode (public) ---
22              
23             sub asn1_decode_pem {
24 4     4 1 9854 my $pem = shift;
25 4 50       20 my $der = pem_to_der($pem) or croak "FATAL: asn1_decode_pem: failed to decode PEM data";
26 4         569 return asn1_decode_der($der, @_);
27             }
28              
29             sub asn1_decode_der_file {
30 2     2 1 2491 my $file = shift;
31 2         11 return asn1_decode_der(read_rawfile($file), @_);
32             }
33              
34             sub asn1_decode_pem_file {
35 2     2 1 6252 my $file = shift;
36 2         12 return asn1_decode_pem(read_rawfile($file), @_);
37             }
38              
39             # --- encode (public) ---
40              
41             sub asn1_encode_der {
42 69     69 1 346084 my ($tree) = @_;
43 69 50       335 croak "FATAL: asn1_encode_der: argument must be an arrayref" unless ref $tree eq 'ARRAY';
44 69         182 my $normalized = [ map { _normalize_node($_) } @$tree ];
  69         192  
45 42         909 return _asn1_encode_der($normalized);
46             }
47              
48             sub asn1_encode_pem {
49 2     2 1 871 my ($tree, $header) = @_;
50 2 50       11 $header = 'DATA' unless defined $header;
51 2         7 my $der = asn1_encode_der($tree);
52 2         25 return der_to_pem($der, $header);
53             }
54              
55             sub asn1_encode_der_file {
56 1     1 1 900 my ($tree, $file) = @_;
57 1         4 my $der = asn1_encode_der($tree);
58 1         9 write_rawfile($file, $der);
59 1         5 return $der;
60             }
61              
62             sub asn1_encode_pem_file {
63 1     1 1 992 my ($tree, $header, $file) = @_;
64 1         6 my $pem = asn1_encode_pem($tree, $header);
65 1         6 write_rawfile($file, $pem);
66 1         6 return $pem;
67             }
68              
69             # --- dump (public) ---
70              
71             sub asn1_to_string {
72 4     4 1 12525 my ($tree) = @_;
73 4 50       18 croak "FATAL: asn1_to_string: argument must be an arrayref" unless ref $tree eq 'ARRAY';
74 4         11 my $out = '';
75 4         14 _dump_nodes(\$out, $tree, 0);
76 4         19 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   49 my ($out, $nodes, $depth) = @_;
90 25         50 for my $node (@$nodes) {
91 58         147 _dump_node($out, $node, $depth);
92             }
93             }
94              
95             sub _dump_node {
96 58     58   104 my ($out, $node, $depth) = @_;
97 58 50       142 my $type = defined $node->{type} ? $node->{type} : '?';
98 58         111 my $val = $node->{value};
99 58 50       116 my $fmt = defined $node->{format} ? $node->{format} : '';
100 58         102 my $indent = ' ' x $depth;
101              
102 58 100 100     222 if ($type eq 'SEQUENCE' || $type eq 'SET') {
103 18 50       45 my $n = ref $val eq 'ARRAY' ? scalar @$val : 0;
104 18         52 $$out .= "${indent}$_LABEL{$type} ($n elem)\n";
105 18 50       64 _dump_nodes($out, $val, $depth + 1) if $n;
106 18         71 return;
107             }
108              
109 40 100       83 if ($type eq 'CUSTOM') {
110 3 50       13 my $cls = defined $node->{class} ? $node->{class} : 'CONTEXT_SPECIFIC';
111 3 50       10 my $tag = defined $node->{tag} ? $node->{tag} : 0;
112 3         7 my $cons = $node->{constructed};
113 3         11 my $label = lc($cls) . " [$tag]";
114 3 50 33     15 if ($cons && ref $val eq 'ARRAY') {
115 3         6 my $n = scalar @$val;
116 3         11 $$out .= "${indent}$label cons ($n elem)\n";
117 3         9 _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         9 return;
122             }
123              
124 37 50       118 my $label = exists $_LABEL{$type} ? $_LABEL{$type} : $type;
125 37         53 $$out .= "${indent}${label}:${\ _dump_value($type, $val, $fmt, $node)}\n";
  37         74  
126             }
127              
128             sub _dump_value {
129 37     37   77 my ($type, $val, $fmt, $node) = @_;
130              
131 37 100       83 return '' if $type eq 'NULL';
132              
133 33 100       89 if ($type eq 'BOOLEAN') {
134 2 100       52 return $val ? 'TRUE' : 'FALSE';
135             }
136 31 100       63 if ($type eq 'INTEGER') {
137 4 50       10 return _dump_value_short($val, $fmt) if $fmt eq 'bytes';
138 4 50       16 my $s = defined $val ? "$val" : '';
139 4 50       29 return length($s) > 64 ? substr($s, 0, 61) . '...' : $s;
140             }
141 27 100       56 if ($type eq 'OID') {
142 10 50       27 my $s = defined $val ? $val : '';
143 10 100       27 $s .= " ($node->{name})" if defined $node->{name};
144 10         42 return $s;
145             }
146 17 100 100     106 if ($type eq 'UTF8_STRING' || $type eq 'PRINTABLE_STRING'
      100        
      100        
147             || $type eq 'IA5_STRING' || $type eq 'TELETEX_STRING') {
148 6 50       14 my $s = defined $val ? $val : '';
149 6 50       33 return length($s) > 64 ? substr($s, 0, 61) . '...' : $s;
150             }
151 11 100 100     58 if ($type eq 'UTCTIME' || $type eq 'GENERALIZEDTIME') {
152 4 50       24 return defined $val ? "$val" : '';
153             }
154 7 50 66     26 if ($type eq 'OCTET_STRING' || $type eq 'BIT_STRING') {
155 7         12 my $extra = '';
156 7 100 66     32 if ($type eq 'BIT_STRING' && defined $node->{bits}) {
157 3         10 $extra = " ($node->{bits} bit)";
158             }
159 7         18 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   17 my ($val, $fmt) = @_;
166 7 50 33     30 return '' unless defined $val && length $val;
167 7         13 my $hex;
168 7 50       19 if ($fmt eq 'hex') {
    100          
169 0         0 $hex = lc($val);
170             } elsif ($fmt eq 'base64') {
171 5         46 $hex = lc unpack("H*", decode_b64($val));
172             } else {
173 2         11 $hex = lc unpack("H*", $val);
174             }
175 7 100       56 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   396 my ($node) = @_;
186 221 50       630 croak "FATAL: asn1_encode: node must be a hashref" unless ref $node eq 'HASH';
187 221 50       653 my $type = defined $node->{type} ? $node->{type} : croak "FATAL: asn1_encode: node missing 'type'";
188 221 100       581 my $fmt = defined $node->{format} ? $node->{format} : '';
189 221         361 my $val = $node->{value};
190              
191             # --- constructed types ---
192 221 100 100     824 if ($type eq 'SEQUENCE' || $type eq 'SET') {
193 63 50       141 croak "FATAL: asn1_encode: $type value must be arrayref" unless ref($val) eq 'ARRAY';
194 63         166 return { type => $type, value => [ map { _normalize_node($_) } @$val ] };
  143         369  
195             }
196             # --- INTEGER: always normalize to decimal string ---
197 158 100       354 if ($type eq 'INTEGER') {
198 25 50       61 croak "FATAL: asn1_encode: INTEGER missing value" unless defined $val;
199 25 100       74 if ($fmt eq 'hex') {
    100          
200 5         39 require Math::BigInt;
201 5         11 my $hex = $val;
202 5         20 my $neg = ($hex =~ s/^-//);
203 5         74 my $bi = Math::BigInt->new("0x$hex");
204 5 100       2195 $bi->bneg() if $neg;
205 5         253 $val = $bi->bstr();
206             }
207             elsif ($fmt eq 'bytes') {
208 3         1955 require Math::BigInt;
209 3         63905 $val = Math::BigInt->new("0x" . unpack("H*", $val))->bstr();
210             }
211             else {
212 17         30 $val = "$val"; # stringify Perl number
213             }
214 25         43858 return { type => 'INTEGER', value => $val };
215             }
216             # --- BOOLEAN ---
217 133 100       304 if ($type eq 'BOOLEAN') {
218 4 100       31 return { type => 'BOOLEAN', value => $val ? 1 : 0 };
219             }
220             # --- NULL ---
221 129 100       267 if ($type eq 'NULL') {
222 11         107 return { type => 'NULL' };
223             }
224             # --- OID ---
225 118 100       273 if ($type eq 'OID') {
226 32 50       87 croak "FATAL: asn1_encode: OID missing value" unless defined $val;
227 32         81 return { type => 'OID', value => _normalize_oid($val) };
228             }
229             # --- OCTET_STRING: always normalize to raw bytes ---
230 86 100       180 if ($type eq 'OCTET_STRING') {
231 21         60 $val = _bin_to_raw($val, $fmt);
232 17         142 return { type => 'OCTET_STRING', value => $val };
233             }
234             # --- BIT_STRING: raw bytes + bits ---
235 65 100       171 if ($type eq 'BIT_STRING') {
236 14         35 $val = _bin_to_raw($val, $fmt);
237             my $bits = exists $node->{bits}
238 14 100       66 ? _normalize_bit_count($node->{bits}, length($val))
239             : (length($val) * 8);
240 11         89 return { type => 'BIT_STRING', value => $val, bits => $bits };
241             }
242             # --- string types ---
243 51 100       119 if ($type eq 'UTF8_STRING') {
244 8 50       120 return { type => 'UTF8_STRING', value => defined $val ? $val : '' };
245             }
246 43 100 100     204 if ($type eq 'IA5_STRING' || $type eq 'PRINTABLE_STRING' || $type eq 'TELETEX_STRING') {
      100        
247 5 50       38 return { type => $type, value => defined $val ? $val : '' };
248             }
249             # --- time types ---
250 38 100       111 if ($type eq 'UTCTIME') {
251 16         46 return { type => 'UTCTIME', value => _normalize_time($val, $fmt, 'utc') };
252             }
253 22 100       47 if ($type eq 'GENERALIZEDTIME') {
254 3         9 return { type => 'GENERALIZEDTIME', value => _normalize_time($val, $fmt, 'gen') };
255             }
256             # --- CUSTOM ---
257 19 50       40 if ($type eq 'CUSTOM') {
258 19 50       55 my $class_name = defined $node->{class} ? $node->{class} : 'CONTEXT_SPECIFIC';
259             croak "FATAL: asn1_encode: invalid CUSTOM class '$class_name'"
260 19 100       401 unless exists $_CLASS_MAP{$class_name};
261 17         37 my $cls = $_CLASS_MAP{$class_name};
262 17 100       43 my $constr = $node->{constructed} ? 1 : 0;
263 17 50       60 my $tag = _normalize_custom_tag(defined $node->{tag} ? $node->{tag} : 0);
264 13         66 my %n = (type => 'CUSTOM', class => $cls, constructed => $constr, tag => $tag);
265 13 100       30 if ($constr) {
266 11 100       180 croak "FATAL: asn1_encode: CUSTOM constructed value must be arrayref"
267             unless ref($val) eq 'ARRAY';
268 10         24 $n{value} = [ map { _normalize_node($_) } @$val ];
  9         26  
269             }
270             else {
271 2 100       155 croak "FATAL: asn1_encode: CUSTOM primitive value must not be a reference"
272             if ref($val);
273 1         4 $n{value} = _bin_to_raw($val, $fmt);
274             }
275 11         106 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   88 my ($val, $fmt) = @_;
283 36 50       82 $val = '' unless defined $val;
284 36 50       102 $fmt = '' unless defined $fmt;
285 36 100       83 if ($fmt eq 'hex') {
286 8 100       381 croak "FATAL: asn1_encode: invalid hex value"
287             unless $val =~ /\A(?:[0-9A-Fa-f]{2})*\z/;
288 6         69 return pack("H*", $val);
289             }
290 28 100       90 if ($fmt eq 'base64') {
291 8 100       448 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         50 return decode_b64($val);
294             }
295 20         54 return $val;
296             }
297              
298             sub _normalize_oid {
299 32     32   73 my ($val) = @_;
300 32         71 my $oid = "$val";
301              
302 32 100       620 croak "FATAL: asn1_encode: invalid OID value '$oid'"
303             unless $oid =~ /\A\d+(?:\.\d+)+\z/;
304              
305 30         145 my @arc = split /\./, $oid;
306 30 50       85 croak "FATAL: asn1_encode: OID must have at least 2 arcs"
307             unless @arc >= 2;
308 30 50 33     176 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     97 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       73 croak "FATAL: asn1_encode: OID has too many arcs (maximum 64)"
313             if @arc > 64;
314              
315 30         132 for my $i (1 .. $#arc) {
316 112 100 100     299 my $limit = ($i == 1 && $arc[0] == 2) ? $_MAX_OID_SECOND_ARC_DEC : $_MAX_ULONG_DEC;
317 112 100       221 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         190 return $oid;
322             }
323              
324             sub _normalize_bit_count {
325 13     13   29 my ($bits, $byte_len) = @_;
326 13         23 my $max_bits = $byte_len * 8;
327              
328 13 50       27 croak "FATAL: asn1_encode: BIT_STRING bits missing"
329             unless defined $bits;
330              
331 13         33 my $raw = "$bits";
332 13 100       400 croak "FATAL: asn1_encode: BIT_STRING bits must be a non-negative integer"
333             unless $raw =~ /\A\d+\z/;
334              
335 11         29 $bits = int($raw);
336 11 100       259 croak "FATAL: asn1_encode: BIT_STRING bits exceeds available data ($bits > $max_bits)"
337             if $bits > $max_bits;
338              
339 10         26 return $bits;
340             }
341              
342             sub _normalize_custom_tag {
343 17     17   36 my ($tag) = @_;
344 17         46 my $raw = "$tag";
345              
346 17 100       15076 croak "FATAL: asn1_encode: CUSTOM tag must be a non-negative integer"
347             unless $raw =~ /\A\d+\z/;
348 14 100       37 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         38 return int($raw);
352             }
353              
354             sub _decimal_fits_limit {
355 126     126   238 my ($value, $limit) = @_;
356 126         180 $value = "$value";
357 126         239 $value =~ s/\A0+(?=\d)//;
358 126 100       417 return 1 if length($value) < length($limit);
359 3 100       154 return 0 if length($value) > length($limit);
360 2         326 return $value le $limit;
361             }
362              
363             sub _max_ulong_dec {
364 2   50 2   186 my $bytes = $Config::Config{longsize} || 0;
365 2 50       12 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   14 my $bytes = $Config::Config{longsize} || 0;
374 2 50       9 return '4294967215' if $bytes == 4;
375 2 50       6 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   57 my ($val, $fmt, $kind) = @_;
384 19 50       41 croak "FATAL: asn1_encode: time value missing" unless defined $val;
385 19 100       48 my $type = $kind eq 'utc' ? 'UTCTIME' : 'GENERALIZEDTIME';
386              
387             # epoch (all digits, possibly negative)
388 19 100 66     173 if ($fmt eq 'epoch' || ($fmt eq '' && $val =~ /^-?\d+$/)) {
      66        
389 5         26 my @t = gmtime($val);
390 5         15 my $year = $t[5] + 1900;
391 5 50       44 if ($kind eq 'utc') {
392 5 100 66     218 croak "FATAL: asn1_encode: UTCTIME year out of range: $year (expected 1950..2049)"
393             if $year < 1950 || $year > 2049;
394             }
395 4 50       58 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     130 if ($fmt eq 'rfc3339' || $val =~ /^\d{4}-/) {
402 10 50       94 $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         93 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         16 my $r;
406 10 100       24 if ($kind eq 'utc') {
407 9 100 66     179 croak "FATAL: asn1_encode: UTCTIME does not allow fractional seconds: $val"
408             if defined $fs && length $fs;
409 8 100 100     348 croak "FATAL: asn1_encode: UTCTIME year out of range: $YYYY (expected 1950..2049)"
410             if $YYYY < 1950 || $YYYY > 2049;
411 6 50       17 my $YY = ($YYYY >= 2000) ? $YYYY - 2000 : $YYYY - 1900;
412 6         36 $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     11 $r .= ".$fs" if defined $fs && $fs > 0;
417             }
418 7 50       22 $r .= ($tz eq 'Z') ? 'Z' : sprintf("%s%02d%02d", $sign, $oh, $om);
419 7         58 return $r;
420             }
421              
422 4         688 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