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