# Copyright (c) 2000-2005 Graham Barr . All rights reserved. # This program is free software; you can redistribute it and/or # modify it under the same terms as Perl itself. package Convert::ASN1; BEGIN { unless (CHECK_UTF8) { local $SIG{__DIE__}; eval { require bytes } and 'bytes'->import } } # These are the subs which do the encoding, they are called with # 0 1 2 3 4 5 # $opt, $op, $stash, $var, $buf, $loop # The order in the array must match the op definitions above my @encode = ( sub { die "internal error\n" }, \&_enc_boolean, \&_enc_integer, \&_enc_bitstring, \&_enc_string, \&_enc_null, \&_enc_object_id, \&_enc_real, \&_enc_sequence, \&_enc_sequence, # SET is the same encoding as sequence \&_enc_time, \&_enc_time, \&_enc_utf8, \&_enc_any, \&_enc_choice, \&_enc_object_id, \&_enc_bcd, ); sub _encode { my ($optn, $ops, $stash, $path) = @_; my $var; foreach my $op (@{$ops}) { if (defined(my $opt = $op->[cOPT])) { next unless defined $stash->{$opt}; } if (defined($var = $op->[cVAR])) { push @$path, $var; require Carp, Carp::croak(join(".", @$path)," is undefined") unless defined $stash->{$var}; } $_[4] .= $op->[cTAG]; &{$encode[$op->[cTYPE]]}( $optn, $op, (UNIVERSAL::isa($stash, 'HASH') ? ($stash, defined($var) ? $stash->{$var} : undef) : ({}, $stash)), $_[4], $op->[cLOOP], $path, ); pop @$path if defined $var; } $_[4]; } sub _enc_boolean { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path $_[4] .= pack("CC",1, $_[3] ? 0xff : 0); } sub _enc_integer { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path if (abs($_[3]) >= 2**31) { my $os = i2osp($_[3], ref($_[3]) || $_[0]->{encode_bigint} || 'Math::BigInt'); my $len = length $os; my $msb = (vec($os, 0, 8) & 0x80) ? 0 : 255; $len++, $os = chr($msb) . $os if $msb xor $_[3] > 0; $_[4] .= asn_encode_length($len); $_[4] .= $os; } else { my $val = int($_[3]); my $neg = ($val < 0); my $len = num_length($neg ? ~$val : $val); my $msb = $val & (0x80 << (($len - 1) * 8)); $len++ if $neg ? !$msb : $msb; $_[4] .= asn_encode_length($len); $_[4] .= substr(pack("N",$val), -$len); } } sub _enc_bitstring { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path my $vref = ref($_[3]) ? \($_[3]->[0]) : \$_[3]; if (CHECK_UTF8 and Encode::is_utf8($$vref)) { utf8::encode(my $tmp = $$vref); $vref = \$tmp; } if (ref($_[3])) { my $less = (8 - ($_[3]->[1] & 7)) & 7; my $len = ($_[3]->[1] + 7) >> 3; $_[4] .= asn_encode_length(1+$len); $_[4] .= chr($less); $_[4] .= substr($$vref, 0, $len); if ($less && $len) { substr($_[4],-1) &= chr((0xff << $less) & 0xff); } } else { $_[4] .= asn_encode_length(1+length $$vref); $_[4] .= chr(0); $_[4] .= $$vref; } } sub _enc_string { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path if (CHECK_UTF8 and Encode::is_utf8($_[3])) { utf8::encode(my $tmp = $_[3]); $_[4] .= asn_encode_length(length $tmp); $_[4] .= $tmp; } else { $_[4] .= asn_encode_length(length $_[3]); $_[4] .= $_[3]; } } sub _enc_null { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path $_[4] .= chr(0); } sub _enc_object_id { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path my @data = ($_[3] =~ /(\d+)/g); if ($_[1]->[cTYPE] == opOBJID) { if(@data < 2) { @data = (0); } else { my $first = $data[1] + ($data[0] * 40); splice(@data,0,2,$first); } } my $l = length $_[4]; $_[4] .= pack("cw*", 0, @data); substr($_[4],$l,1) = asn_encode_length(length($_[4]) - $l - 1); } sub _enc_real { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path # Zero unless ($_[3]) { $_[4] .= chr(0); return; } require POSIX; # +oo (well we use HUGE_VAL as Infinity is not avaliable to perl) if ($_[3] >= POSIX::HUGE_VAL()) { $_[4] .= pack("C*",0x01,0x40); return; } # -oo (well we use HUGE_VAL as Infinity is not avaliable to perl) if ($_[3] <= - POSIX::HUGE_VAL()) { $_[4] .= pack("C*",0x01,0x41); return; } if (exists $_[0]->{'encode_real'} && $_[0]->{'encode_real'} ne 'binary') { my $tmp = sprintf("%g",$_[3]); $_[4] .= asn_encode_length(1+length $tmp); $_[4] .= chr(1); # NR1? $_[4] .= $tmp; return; } # We have a real number. my $first = 0x80; my($mantissa, $exponent) = POSIX::frexp($_[3]); if ($mantissa < 0.0) { $mantissa = -$mantissa; $first |= 0x40; } my($eMant,$eExp); while($mantissa > 0.0) { ($mantissa, my $int) = POSIX::modf($mantissa * (1<<8)); $eMant .= chr($int); } $exponent -= 8 * length $eMant; _enc_integer(undef, undef, undef, $exponent, $eExp); # $eExp will br prefixed by a length byte if (5 > length $eExp) { $eExp =~ s/\A.//s; $first |= length($eExp)-1; } else { $first |= 0x3; } $_[4] .= asn_encode_length(1 + length($eMant) + length($eExp)); $_[4] .= chr($first); $_[4] .= $eExp; $_[4] .= $eMant; } sub _enc_sequence { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path if (my $ops = $_[1]->[cCHILD]) { my $l = length $_[4]; $_[4] .= "\0\0"; # guess if (defined $_[5]) { my $op = $ops->[0]; # there should only be one my $enc = $encode[$op->[cTYPE]]; my $tag = $op->[cTAG]; my $loop = $op->[cLOOP]; push @{$_[6]}, -1; foreach my $var (@{$_[3]}) { $_[6]->[-1]++; $_[4] .= $tag; &{$enc}( $_[0], # $optn $op, # $op $_[2], # $stash $var, # $var $_[4], # $buf $loop, # $loop $_[6], # $path ); } pop @{$_[6]}; } else { _encode($_[0],$_[1]->[cCHILD], defined($_[3]) ? $_[3] : $_[2], $_[6], $_[4]); } substr($_[4],$l,2) = asn_encode_length(length($_[4]) - $l - 2); } else { $_[4] .= asn_encode_length(length $_[3]); $_[4] .= $_[3]; } } my %_enc_time_opt = ( utctime => 1, withzone => 0, raw => 2); sub _enc_time { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path my $mode = $_enc_time_opt{$_[0]->{'encode_time'} || ''} || 0; if ($mode == 2) { $_[4] .= asn_encode_length(length $_[3]); $_[4] .= $_[3]; return; } my @time; my $offset; my $isgen = $_[1]->[cTYPE] == opGTIME; if (ref($_[3])) { $offset = int($_[3]->[1] / 60); $time = $_[3]->[0] + $_[3]->[1]; } elsif ($mode == 0) { if (exists $_[0]->{'encode_timezone'}) { $offset = int($_[0]->{'encode_timezone'} / 60); $time = $_[3] + $_[0]->{'encode_timezone'}; } else { @time = localtime($_[3]); my @g = gmtime($_[3]); $offset = ($time[1] - $g[1]) + ($time[2] - $g[2]) * 60; $time = $_[3] + $offset*60; } } else { $time = $_[3]; } @time = gmtime($time); $time[4] += 1; $time[5] = $isgen ? ($time[5] + 1900) : ($time[5] % 100); my $tmp = sprintf("%02d"x6, @time[5,4,3,2,1,0]); if ($isgen) { my $sp = sprintf("%.03f",$time); $tmp .= substr($sp,-4) unless $sp =~ /\.000$/; } $tmp .= $offset ? sprintf("%+03d%02d",$offset / 60, abs($offset % 60)) : 'Z'; $_[4] .= asn_encode_length(length $tmp); $_[4] .= $tmp; } sub _enc_utf8 { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path if (CHECK_UTF8) { my $tmp = $_[3]; utf8::upgrade($tmp) unless Encode::is_utf8($tmp); utf8::encode($tmp); $_[4] .= asn_encode_length(length $tmp); $_[4] .= $tmp; } else { $_[4] .= asn_encode_length(length $_[3]); $_[4] .= $_[3]; } } sub _enc_any { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path my $handler; if ($_[1]->[cDEFINE] && $_[2]->{$_[1]->[cDEFINE]}) { $handler=$_[0]->{oidtable}{$_[2]->{$_[1]->[cDEFINE]}}; $handler=$_[0]->{handlers}{$_[1]->[cVAR]}{$_[2]->{$_[1]->[cDEFINE]}} unless $handler; } if ($handler) { $_[4] .= $handler->encode($_[3]); } else { $_[4] .= $_[3]; } } sub _enc_choice { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path my $stash = defined($_[3]) ? $_[3] : $_[2]; for my $op (@{$_[1]->[cCHILD]}) { my $var = defined $op->[cVAR] ? $op->[cVAR] : $op->[cCHILD]->[0]->[cVAR]; if (exists $stash->{$var}) { push @{$_[6]}, $var; _encode($_[0],[$op], $stash, $_[6], $_[4]); pop @{$_[6]}; return; } } require Carp; Carp::croak("No value found for CHOICE " . join(".", @{$_[6]})); } sub _enc_bcd { # 0 1 2 3 4 5 6 # $optn, $op, $stash, $var, $buf, $loop, $path my $str = ("$_[3]" =~ /^(\d+)/) ? $1 : ""; $str .= "F" if length($str) & 1; $_[4] .= asn_encode_length(length($str) / 2); $_[4] .= pack("H*", $str); } 1;