1 #-----------------------------------------------------------------------------
2 # Copyright (C) 1999-2004 Jochen C. Loewer (loewerj@web.de)
3 # Copyright (C) 2004-2006 Michael Schlenker (mic42@users.sourceforge.net)
4 #-----------------------------------------------------------------------------
6 # A partial ASN decoder/encoder implementation in plain Tcl.
8 # See ASN.1 (X.680) and BER (X.690).
9 # See 'asn_ber_intro.txt' in this directory.
11 # This software is copyrighted by Jochen C. Loewer (loewerj@web.de). The
12 # following terms apply to all files associated with the software unless
13 # explicitly disclaimed in individual files.
15 # The authors hereby grant permission to use, copy, modify, distribute,
16 # and license this software and its documentation for any purpose, provided
17 # that existing copyright notices are retained in all copies and that this
18 # notice is included verbatim in any distributions. No written agreement,
19 # license, or royalty fee is required for any of the authorized uses.
20 # Modifications to this software may be copyrighted by their authors
21 # and need not follow the licensing terms described here, provided that
22 # the new terms are clearly indicated on the first page of each file where
25 # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
26 # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
27 # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
28 # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
31 # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
32 # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
33 # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
34 # IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
35 # NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
38 # written by Jochen Loewer
41 # $Id: asn.tcl,v 1.1 2012-04-04 10:50:38 igus Exp $
43 #-----------------------------------------------------------------------------
45 # needed for using wide()
46 package require Tcl 8.4
55 asnApplicationConstr \
84 asnGetPrintableString \
88 asnGetObjectIdentifier \
94 # general BER utility commands
102 #-----------------------------------------------------------------------------
103 # Implementation notes:
105 # See the 'asn_ber_intro.txt' in this directory for an introduction
106 # into BER/DER encoding of ASN.1 information. Bibliography information
108 # A Layman's Guide to a Subset of ASN.1, BER, and DER
110 # An RSA Laboratories Technical Note
111 # Burton S. Kaliski Jr.
112 # Revised November 1, 1993
114 # Supersedes June 3, 1991 version, which was also published as
115 # NIST/OSI Implementors' Workshop document SEC-SIG-91-17.
116 # PKCS documents are available by electronic mail to
119 # Copyright (C) 1991-1993 RSA Laboratories, a division of RSA
120 # Data Security, Inc. License to copy this document is granted
121 # provided that it is identified as "RSA Data Security, Inc.
122 # Public-Key Cryptography Standards (PKCS)" in all material
123 # mentioning or referencing this document.
124 # 003-903015-110-000-000
126 #-----------------------------------------------------------------------------
128 #-----------------------------------------------------------------------------
129 # asnLength : Encode some length data. Helper command.
130 #-----------------------------------------------------------------------------
132 proc ::asn::asnLength {len} {
135 return -code error "Negative length octet requested"
138 # short form: ISO X.690 8.1.3.4
139 return [binary format c $len]
141 # long form: ISO X.690 8.1.3.5
142 # try to use a minimal encoding,
143 # even if not required by BER, but it is required by DER
144 # take care for signed vs. unsigned issues
146 return [binary format H2c 81 [expr {$len - 256}]]
149 # two octet signed value
150 return [binary format H2S 82 $len]
153 return [binary format H2S 82 [expr {$len - 65536}]]
155 if {$len < 8388608} {
156 # three octet signed value
157 return [binary format H2cS 83 [expr {$len >> 16}] [expr {($len & 0xFFFF) - 65536}]]
159 if {$len < 16777216} {
160 # three octet signed value
161 return [binary format H2cS 83 [expr {($len >> 16) -256}] [expr {($len & 0xFFFF) -65536}]]
163 if {$len < 2147483649} {
164 # four octet signed value
165 return [binary format H2I 84 $len]
167 if {$len < 4294967296} {
168 # four octet unsigned value
169 return [binary format H2I 84 [expr {$len - 4294967296}]]
171 if {$len < 1099511627776} {
172 # five octet unsigned value
173 return [binary format H2 85][string range [binary format W $len] 3 end]
175 if {$len < 281474976710656} {
176 # six octet unsigned value
177 return [binary format H2 86][string range [binary format W $len] 2 end]
179 if {$len < 72057594037927936} {
181 return [binary format H2 87][string range [binary format W $len] 1 end]
184 # must be a 64-bit wide signed value
185 return [binary format H2W 88 $len]
188 #-----------------------------------------------------------------------------
189 # asnSequence : Assumes that the arguments are already ASN encoded.
190 #-----------------------------------------------------------------------------
192 proc ::asn::asnSequence {args} {
193 asnSequenceFromList $args
196 proc ::asn::asnSequenceFromList {lst} {
197 # The sequence tag is 0x30. The length is arbitrary and thus full
198 # length coding is required. The arguments have to be BER encoded
199 # already. Constructed value, definite-length encoding.
205 set len [string length $out]
206 return [binary format H2a*a$len 30 [asnLength $len] $out]
210 #-----------------------------------------------------------------------------
211 # asnSet : Assumes that the arguments are already ASN encoded.
212 #-----------------------------------------------------------------------------
214 proc ::asn::asnSet {args} {
218 proc ::asn::asnSetFromList {lst} {
219 # The set tag is 0x31. The length is arbitrary and thus full
220 # length coding is required. The arguments have to be BER encoded
227 set len [string length $out]
228 return [binary format H2a*a$len 31 [asnLength $len] $out]
232 #-----------------------------------------------------------------------------
233 # asnApplicationConstr
234 #-----------------------------------------------------------------------------
236 proc ::asn::asnApplicationConstr {appNumber args} {
237 # Packs the arguments into a constructed value with application tag.
243 set code [expr {0x060 + $appNumber}]
244 set len [string length $out]
245 return [binary format ca*a$len $code [asnLength $len] $out]
248 #-----------------------------------------------------------------------------
250 #-----------------------------------------------------------------------------
252 proc ::asn::asnApplication {appNumber data} {
253 # Packs the arguments into a constructed value with application tag.
255 set code [expr {0x040 + $appNumber}]
256 set len [string length $data]
257 return [binary format ca*a$len $code [asnLength $len] $data]
260 #-----------------------------------------------------------------------------
262 #-----------------------------------------------------------------------------
264 proc ::asn::asnContextConstr {contextNumber args} {
265 # Packs the arguments into a constructed value with application tag.
271 set code [expr {0x0A0 + $contextNumber}]
272 set len [string length $out]
273 return [binary format ca*a$len $code [asnLength $len] $out]
276 #-----------------------------------------------------------------------------
278 #-----------------------------------------------------------------------------
280 proc ::asn::asnContext {contextNumber data} {
281 # Packs the arguments into a constructed value with application tag.
282 set code [expr {0x080 + $contextNumber}]
283 set len [string length $data]
284 return [binary format ca*a$len $code [asnLength $len] $data]
286 #-----------------------------------------------------------------------------
288 #-----------------------------------------------------------------------------
290 proc ::asn::asnChoice {appNumber args} {
291 # Packs the arguments into a choice construction.
297 set code [expr {0x080 + $appNumber}]
298 set len [string length $out]
299 return [binary format ca*a$len $code [asnLength $len] $out]
302 #-----------------------------------------------------------------------------
304 #-----------------------------------------------------------------------------
306 proc ::asn::asnChoiceConstr {appNumber args} {
307 # Packs the arguments into a choice construction.
313 set code [expr {0x0A0 + $appNumber}]
314 set len [string length $out]
315 return [binary format ca*a$len $code [asnLength $len] $out]
318 #-----------------------------------------------------------------------------
319 # asnInteger : Encode integer value.
320 #-----------------------------------------------------------------------------
322 proc ::asn::asnInteger {number} {
323 asnIntegerOrEnum 02 $number
326 #-----------------------------------------------------------------------------
327 # asnEnumeration : Encode enumeration value.
328 #-----------------------------------------------------------------------------
330 proc ::asn::asnEnumeration {number} {
331 asnIntegerOrEnum 0a $number
334 #-----------------------------------------------------------------------------
335 # asnIntegerOrEnum : Common code for Integers and Enumerations
336 # No Bignum version, as we do not expect large Enums.
337 #-----------------------------------------------------------------------------
339 proc ::asn::asnIntegerOrEnum {tag number} {
340 # The integer tag is 0x02 , the Enum Tag 0x0a otherwise identical.
341 # The length is 1, 2, 3, or 4, coded in a
342 # single byte. This can be done directly, no need to go through
343 # asnLength. The value itself is written in big-endian.
345 # Known bug/issue: The command cannot handle very wide integers, i.e.
346 # anything above 8 bytes length. Use asnBignumInteger for those.
348 # check if we really have an int
352 if {($number >= -128) && ($number < 128)} {
353 return [binary format H2H2c $tag 01 $number]
355 if {($number >= -32768) && ($number < 32768)} {
356 return [binary format H2H2S $tag 02 $number]
358 if {($number >= -8388608) && ($number < 8388608)} {
359 set numberb [expr {$number & 0xFFFF}]
360 set numbera [expr {($number >> 16) & 0xFF}]
361 return [binary format H2H2cS $tag 03 $numbera $numberb]
363 if {($number >= -2147483648) && ($number < 2147483648)} {
364 return [binary format H2H2I $tag 04 $number]
366 if {($number >= -549755813888) && ($number < 549755813888)} {
367 set numberb [expr {$number & 0xFFFFFFFF}]
368 set numbera [expr {($number >> 32) & 0xFF}]
369 return [binary format H2H2cI $tag 05 $numbera $numberb]
371 if {($number >= -140737488355328) && ($number < 140737488355328)} {
372 set numberb [expr {$number & 0xFFFFFFFF}]
373 set numbera [expr {($number >> 32) & 0xFFFF}]
374 return [binary format H2H2SI $tag 06 $numbera $numberb]
376 if {($number >= -36028797018963968) && ($number < 36028797018963968)} {
377 set numberc [expr {$number & 0xFFFFFFFF}]
378 set numberb [expr {($number >> 32) & 0xFFFF}]
379 set numbera [expr {($number >> 48) & 0xFF}]
380 return [binary format H2H2cSI $tag 07 $numbera $numberb $numberc]
382 if {($number >= -9223372036854775808) && ($number <= 9223372036854775807)} {
383 return [binary format H2H2W $tag 08 $number]
385 return -code error "Integer value to large to encode, use asnBigInteger"
388 #-----------------------------------------------------------------------------
389 # asnBigInteger : Encode a long integer value using math::bignum
390 #-----------------------------------------------------------------------------
392 proc ::asn::asnBigInteger {bignum} {
393 # require math::bignum only if it is used
394 package require math::bignum
396 # this is a hack to check for bignum...
397 if {[llength $bignum] < 2 || ([lindex $bignum 0] ne "bignum")} {
398 return -code error "expected math::bignum value got \"$bignum\""
400 if {[math::bignum::sign $bignum]} {
401 # generate two's complement form
402 set bits [math::bignum::bits $bignum]
403 set padding [expr {$bits % 8}]
404 set len [expr {int(ceil($bits / 8.0))}]
406 # we need a complete extra byte for the sign
407 # unless this is a base 2 multiple
408 set test [math::bignum::fromstr 0]
409 math::bignum::setbit test [expr {$bits-1}]
410 if {[math::bignum::ne [math::bignum::abs $bignum] $test]} {
414 set exp [math::bignum::pow \
415 [math::bignum::fromstr 256] \
416 [math::bignum::fromstr $len]]
417 set bignum [math::bignum::add $bignum $exp]
418 set hex [math::bignum::tostr $bignum 16]
420 set bits [math::bignum::bits $bignum]
421 if {($bits % 8) == 0 && $bits > 0} {
426 set hex $pad[math::bignum::tostr $bignum 16]
428 if {[string length $hex]%2} {
431 set octets [expr {(([string length $hex]+1)/2)}]
432 return [binary format H2a*H* 02 [asnLength $octets] $hex]
436 #-----------------------------------------------------------------------------
437 # asnBoolean : Encode a boolean value.
438 #-----------------------------------------------------------------------------
440 proc ::asn::asnBoolean {bool} {
441 # The boolean tag is 0x01. The length is always 1, coded in
442 # a single byte. This can be done directly, no need to go through
443 # asnLength. The value itself is written in big-endian.
445 return [binary format H2H2c 01 01 [expr {$bool ? 0x0FF : 0x0}]]
448 #-----------------------------------------------------------------------------
449 # asnOctetString : Encode a string of arbitrary bytes
450 #-----------------------------------------------------------------------------
452 proc ::asn::asnOctetString {string} {
453 # The octet tag is 0x04. The length is arbitrary, so we need
454 # 'asnLength' for full coding of the length.
456 set len [string length $string]
457 return [binary format H2a*a$len 04 [asnLength $len] $string]
460 #-----------------------------------------------------------------------------
461 # asnNull : Encode a null value
462 #-----------------------------------------------------------------------------
464 proc ::asn::asnNull {} {
465 # Null has only one valid encoding
469 #-----------------------------------------------------------------------------
470 # asnBitstring : Encode a Bit String value
471 #-----------------------------------------------------------------------------
473 proc ::asn::asnBitString {bitstring} {
474 # The bit string tag is 0x03.
475 # Bit strings can be either simple or constructed
476 # we always use simple encoding
478 set bitlen [string length $bitstring]
479 set padding [expr {(8 - ($bitlen % 8)) % 8}]
480 set len [expr {($bitlen / 8) + 1}]
481 if {$padding != 0} {incr len}
483 return [binary format H2a*B* 03 [asnLength $len] $bitstring]
486 #-----------------------------------------------------------------------------
487 # asnUTCTime : Encode an UTC time string
488 #-----------------------------------------------------------------------------
490 proc ::asn::asnUTCTime {UTCtimestring} {
491 # the utc time tag is 0x17.
493 # BUG: we do not check the string for well formedness
495 set ascii [encoding convertto ascii $UTCtimestring]
496 set len [string length $ascii]
497 return [binary format H2a*a* 17 [asnLength $len] $ascii]
500 #-----------------------------------------------------------------------------
501 # asnPrintableString : Encode a printable string
502 #-----------------------------------------------------------------------------
504 variable nonPrintableChars {[^ A-Za-z0-9'()+,.:/?=-]}
506 proc ::asn::asnPrintableString {string} {
507 # the printable string tag is 0x13
508 variable nonPrintableChars
509 # it is basically a restricted ascii string
510 if {[regexp $nonPrintableChars $string ]} {
511 return -code error "Illegal character in PrintableString."
515 set ascii [encoding convertto ascii $string]
516 return [asnEncodeString 13 $ascii]
519 #-----------------------------------------------------------------------------
520 # asnIA5String : Encode an Ascii String
521 #-----------------------------------------------------------------------------
522 proc ::asn::asnIA5String {string} {
523 # the IA5 string tag is 0x16
524 # check for extended charachers
525 if {[string length $string]!=[string bytelength $string]} {
526 return -code error "Illegal character in IA5String"
528 set ascii [encoding convertto ascii $string]
529 return [asnEncodeString 16 $ascii]
532 #-----------------------------------------------------------------------------
533 # asnNumericString : Encode a Numeric String type
534 #-----------------------------------------------------------------------------
536 variable nonNumericChars {[^0-9 ]}
538 proc ::asn::asnNumericString {string} {
539 # the Numeric String type has tag 0x12
540 variable nonNumericChars
541 if {[regexp $nonNumericChars $string]} {
542 return -code error "Illegal character in Numeric String."
545 return [asnEncodeString 12 $string]
547 #----------------------------------------------------------------------
548 # asnBMPString: Encode a Tcl string as Basic Multinligval (UCS2) string
549 #-----------------------------------------------------------------------
550 proc asn::asnBMPString {string} {
551 if {$::tcl_platform(byteOrder) eq "littleEndian"} {
553 foreach {lo hi} [split [encoding convertto unicode $string] ""] {
557 set bytes [encoding convertto unicode $string]
559 return [asnEncodeString 1e $bytes]
561 #---------------------------------------------------------------------------
562 # asnUTF8String: encode tcl string as UTF8 String
563 #----------------------------------------------------------------------------
564 proc asn::asnUTF8String {string} {
565 return [asnEncodeString 0c [encoding convertto utf-8 $string]]
567 #-----------------------------------------------------------------------------
568 # asnEncodeString : Encode an RestrictedCharacter String
569 #-----------------------------------------------------------------------------
570 proc ::asn::asnEncodeString {tag string} {
571 set len [string length $string]
572 return [binary format H2a*a$len $tag [asnLength $len] $string]
575 #-----------------------------------------------------------------------------
576 # asnObjectIdentifier : Encode an Object Identifier value
577 #-----------------------------------------------------------------------------
578 proc ::asn::asnObjectIdentifier {oid} {
579 # the object identifier tag is 0x06
581 if {[llength $oid] < 2} {
582 return -code error "OID must have at least two subidentifiers."
585 # basic check that it is valid
586 foreach identifier $oid {
587 if {$identifier < 0} {
589 "Malformed OID. Identifiers must be positive Integers."
593 if {[lindex $oid 0] > 2} {
594 return -code error "First subidentifier must be 0,1 or 2"
596 if {[lindex $oid 1] > 39} {
598 "Second subidentifier must be between 0 and 39"
601 # handle the special cases directly
602 switch [llength $oid] {
603 2 { return [binary format H2H2c 06 01 \
604 [expr {[lindex $oid 0]*40+[lindex $oid 1]}]] }
606 # This can probably be written much shorter.
607 # Just a first try that works...
609 set octets [binary format c \
610 [expr {[lindex $oid 0]*40+[lindex $oid 1]}]]
611 foreach identifier [lrange $oid 2 end] {
613 if {$identifier < 128} {
614 set subidentifier [list $identifier]
616 set subidentifier [list]
617 # find the largest divisor
619 while {($identifier / $d) >= 128} {
620 set d [expr {$d * 128}]
622 # and construct the subidentifiers
623 set remainder $identifier
625 set coefficient [expr {($remainder / $d) | 0x80}]
626 set remainder [expr {$remainder % $d}]
627 set d [expr {$d / 128}]
628 lappend subidentifier $coefficient
630 lappend subidentifier $remainder
632 append octets [binary format c* $subidentifier]
634 return [binary format H2a*a* 06 \
635 [asnLength [string length $octets]] $octets]
641 #-----------------------------------------------------------------------------
642 # asnGetResponse : Read a ASN response from a channel.
643 #-----------------------------------------------------------------------------
645 proc ::asn::asnGetResponse {sock data_var} {
648 # We expect a sequence here (tag 0x30). The code below is an
649 # inlined replica of 'asnGetSequence', modified for reading from a
650 # channel instead of a string.
652 set tag [read $sock 1]
654 if {$tag == "\x30"} {
655 # The following code is a replica of 'asnGetLength', modified
656 # for reading the bytes from the channel instead of a string.
658 set len1 [read $sock 1]
659 binary scan $len1 c num
660 set length [expr {($num + 0x100) % 0x100}]
662 if {$length >= 0x080} {
663 # The byte the read is not the length, but a prefix, and
664 # the lower nibble tells us how many bytes follow.
666 set len_length [expr {$length & 0x7f}]
668 # BUG: We should not perform the value extraction for an
669 # BUG: improper length. It wastes cycles, and here it can
670 # BUG: cause us trouble, reading more data than there is
671 # BUG: on the channel. Depending on the channel
672 # BUG: configuration an attacker can induce us to block,
673 # BUG: causing a denial of service.
674 set lengthBytes [read $sock $len_length]
678 binary scan $lengthBytes c length
679 set length [expr {($length + 0x100) % 0x100}]
681 2 { binary scan $lengthBytes S length }
682 3 { binary scan \x00$lengthBytes I length }
683 4 { binary scan $lengthBytes I length }
686 "length information too long ($len_length)"
691 # Now that the length is known we get the remainder,
692 # i.e. payload, and construct proper in-memory BER encoded
695 set rest [read $sock $length]
696 set data [binary format aa*a$length $tag [asnLength $length] $rest]
698 # Generate an error message if the data is not a sequence as
702 binary scan $tag H2 tag_hex
703 return -code error "unknown start tag [string length $tag] $tag_hex"
707 #-----------------------------------------------------------------------------
708 # asnGetByte : Retrieve a single byte from the data (unsigned)
709 #-----------------------------------------------------------------------------
711 proc ::asn::asnGetByte {data_var byte_var} {
712 upvar $data_var data $byte_var byte
714 binary scan [string index $data 0] c byte
715 set byte [expr {($byte + 0x100) % 0x100}]
716 set data [string range $data 1 end]
721 #-----------------------------------------------------------------------------
722 # asnPeekByte : Retrieve a single byte from the data (unsigned)
723 # without removing it.
724 #-----------------------------------------------------------------------------
726 proc ::asn::asnPeekByte {data_var byte_var} {
727 upvar $data_var data $byte_var byte
729 binary scan [string index $data 0] c byte
730 set byte [expr {($byte + 0x100) % 0x100}]
735 #-----------------------------------------------------------------------------
736 # ansRetag: Remove an explicit tag with the real newTag
738 #-----------------------------------------------------------------------------
739 proc ::asn::asnRetag {data_var newTag} {
740 upvar 1 $data_var data
742 set data [binary format c $newTag]$data
745 #-----------------------------------------------------------------------------
746 # asnGetBytes : Retrieve a block of 'length' bytes from the data.
747 #-----------------------------------------------------------------------------
749 proc ::asn::asnGetBytes {data_var length bytes_var} {
750 upvar $data_var data $bytes_var bytes
753 set bytes [string range $data 0 $length]
755 set data [string range $data $length end]
761 #-----------------------------------------------------------------------------
762 # asnGetLength : Decode an ASN length value (See notes)
763 #-----------------------------------------------------------------------------
765 proc ::asn::asnGetLength {data_var length_var} {
766 upvar $data_var data $length_var length
768 asnGetByte data length
769 if {$length == 0x080} {
770 return -code error "Indefinite length BER encoding not yet supported"
772 if {$length > 0x080} {
773 # The retrieved byte is a prefix value, and the integer in the
774 # lower nibble tells us how many bytes were used to encode the
775 # length data following immediately after this prefix.
777 set len_length [expr {$length & 0x7f}]
779 if {[string length $data] < $len_length} {
781 "length information invalid, not enough octets left"
784 asnGetBytes data $len_length lengthBytes
788 # Efficiently coded data will not go through this
789 # path, as small length values can be coded directly,
792 binary scan $lengthBytes c length
793 set length [expr {($length + 0x100) % 0x100}]
795 2 { binary scan $lengthBytes S length
796 set length [expr {($length + 0x10000) % 0x10000}]
798 3 { binary scan \x00$lengthBytes I length
799 set length [expr {($length + 0x1000000) % 0x1000000}]
801 4 { binary scan $lengthBytes I length
802 set length [expr {(wide($length) + 0x100000000) % 0x100000000}]
805 binary scan $lengthBytes H* hexstr
806 # skip leading zeros which are allowed by BER
807 set hexlen [string trimleft $hexstr 0]
808 # check if it fits into a 64-bit signed integer
809 if {[string length $hexlen] > 16} {
810 return -code error -errorcode {ARITH IOVERFLOW
811 {Length value too large for normal use, try asnGetBigLength}} \
812 "Length value to large"
813 } elseif { [string length $hexlen] == 16 \
814 && ([string index $hexlen 0] & 0x8)} {
815 # check most significant bit, if set we need bignum
816 return -code error -errorcode {ARITH IOVERFLOW
817 {Length value too large for normal use, try asnGetBigLength}} \
818 "Length value to large"
820 scan $hexstr "%lx" length
829 #-----------------------------------------------------------------------------
830 # asnGetBigLength : Retrieve a length that can not be represented in 63-bit
831 #-----------------------------------------------------------------------------
833 proc ::asn::asnGetBigLength {data_var biglength_var} {
835 # Does any real world code really need this?
836 # If we encounter this, we are doomed to fail anyway,
837 # (there would be an Exabyte inside the data_var, )
839 # So i implement it just for completness.
841 package require math::bignum
843 upvar $data_var data $length_var length
845 asnGetByte data length
846 if {$length == 0x080} {
847 return -code error "Indefinite length BER encoding not yet supported"
849 if {$length > 0x080} {
850 # The retrieved byte is a prefix value, and the integer in the
851 # lower nibble tells us how many bytes were used to encode the
852 # length data following immediately after this prefix.
854 set len_length [expr {$length & 0x7f}]
856 if {[string length $data] < $len_length} {
858 "length information invalid, not enough octets left"
861 asnGetBytes data $len_length lengthBytes
862 binary scan $lengthBytes H* hexlen
863 set length [math::bignum::fromstr $hexlen 16]
868 #-----------------------------------------------------------------------------
869 # asnGetInteger : Retrieve integer.
870 #-----------------------------------------------------------------------------
872 proc ::asn::asnGetInteger {data_var int_var} {
875 upvar $data_var data $int_var int
881 [format "Expected Integer (0x02), but got %02x" $tag]
884 asnGetLength data len
885 asnGetBytes data $len integerBytes
890 1 { binary scan $integerBytes c int }
891 2 { binary scan $integerBytes S int }
893 # check for negative int and pad
894 scan [string index $integerBytes 0] %c byte
896 binary scan \xff$integerBytes I int
898 binary scan \x00$integerBytes I int
901 4 { binary scan $integerBytes I int }
906 # check for negative int and pad
907 scan [string index $integerBytes 0] %c byte
909 set pad [string repeat \xff [expr {8-$len}]]
911 set pad [string repeat \x00 [expr {8-$len}]]
913 binary scan $pad$integerBytes W int
916 # Too long, or prefix coding was used.
917 return -code error "length information too long"
923 #-----------------------------------------------------------------------------
924 # asnGetBigInteger : Retrieve a big integer.
925 #-----------------------------------------------------------------------------
927 proc ::asn::asnGetBigInteger {data_var bignum_var} {
928 # require math::bignum only if it is used
929 package require math::bignum
931 # Tag is 0x02. We expect that the length of the integer is coded with
932 # maximal efficiency, i.e. without a prefix 0x81 prefix. If a prefix
933 # is used this decoder will fail.
935 upvar $data_var data $bignum_var bignum
941 [format "Expected Integer (0x02), but got %02x" $tag]
944 asnGetLength data len
945 asnGetBytes data $len integerBytes
947 binary scan $integerBytes H* hex
948 set bignum [math::bignum::fromstr $hex 16]
949 set bits [math::bignum::bits $bignum]
950 set exp [math::bignum::pow \
951 [math::bignum::fromstr 2] \
952 [math::bignum::fromstr $bits]]
953 set big [math::bignum::sub $bignum $exp]
961 #-----------------------------------------------------------------------------
962 # asnGetEnumeration : Retrieve an enumeration id
963 #-----------------------------------------------------------------------------
965 proc ::asn::asnGetEnumeration {data_var enum_var} {
966 # This is like 'asnGetInteger', except for a different tag.
968 upvar $data_var data $enum_var enum
974 [format "Expected Enumeration (0x0a), but got %02x" $tag]
977 asnGetLength data len
978 asnGetBytes data $len integerBytes
982 1 { binary scan $integerBytes c enum }
983 2 { binary scan $integerBytes S enum }
984 3 { binary scan \x00$integerBytes I enum }
985 4 { binary scan $integerBytes I enum }
987 return -code error "length information too long"
993 #-----------------------------------------------------------------------------
994 # asnGetOctetString : Retrieve arbitrary string.
995 #-----------------------------------------------------------------------------
997 proc ::asn::asnGetOctetString {data_var string_var} {
998 # Here we need the full decoder for length data.
1000 upvar $data_var data $string_var string
1004 return -code error \
1005 [format "Expected Octet String (0x04), but got %02x" $tag]
1007 asnGetLength data length
1008 asnGetBytes data $length temp
1013 #-----------------------------------------------------------------------------
1014 # asnGetSequence : Retrieve Sequence data for further decoding.
1015 #-----------------------------------------------------------------------------
1017 proc ::asn::asnGetSequence {data_var sequence_var} {
1018 # Here we need the full decoder for length data.
1020 upvar $data_var data $sequence_var sequence
1023 if {$tag != 0x030} {
1024 return -code error \
1025 [format "Expected Sequence (0x30), but got %02x" $tag]
1027 asnGetLength data length
1028 asnGetBytes data $length temp
1033 #-----------------------------------------------------------------------------
1034 # asnGetSet : Retrieve Set data for further decoding.
1035 #-----------------------------------------------------------------------------
1037 proc ::asn::asnGetSet {data_var set_var} {
1038 # Here we need the full decoder for length data.
1040 upvar $data_var data $set_var set
1043 if {$tag != 0x031} {
1044 return -code error \
1045 [format "Expected Set (0x31), but got %02x" $tag]
1047 asnGetLength data length
1048 asnGetBytes data $length temp
1053 #-----------------------------------------------------------------------------
1055 #-----------------------------------------------------------------------------
1057 proc ::asn::asnGetApplication {data_var appNumber_var {content_var {}} {constructed_var {}}} {
1058 upvar $data_var data $appNumber_var appNumber
1061 asnGetLength data length
1063 if {($tag & 0xC0) != 0x040} {
1064 return -code error \
1065 [format "Expected Application (0x60 or 0x40), but got %02x" $tag]
1067 set appNumber [expr {$tag & 0x1F}]
1068 if {[string length $constructed_var]} {
1069 upvar 1 $constructed_var constructed
1070 set constructed [expr {$tag & 0x20}]
1072 if {[string length $content_var]} {
1073 upvar 1 $content_var content
1074 asnGetBytes data $length content
1079 #-----------------------------------------------------------------------------
1080 # asnGetBoolean: decode a boolean value
1081 #-----------------------------------------------------------------------------
1083 proc asn::asnGetBoolean {data_var bool_var} {
1084 upvar $data_var data $bool_var bool
1088 return -code error \
1089 [format "Expected Boolean (0x01), but got %02x" $tag]
1092 asnGetLength data length
1093 asnGetByte data byte
1094 set bool [expr {$byte == 0 ? 0 : 1}]
1098 #-----------------------------------------------------------------------------
1099 # asnGetUTCTime: Extract an UTC Time string from the data. Returns a string
1100 # representing an UTC Time.
1102 #-----------------------------------------------------------------------------
1104 proc asn::asnGetUTCTime {data_var utc_var} {
1105 upvar $data_var data $utc_var utc
1109 return -code error \
1110 [format "Expected UTCTime (0x17), but got %02x" $tag]
1113 asnGetLength data length
1114 asnGetBytes data $length bytes
1116 # this should be ascii, make it explicit
1117 set bytes [encoding convertfrom ascii $bytes]
1118 binary scan $bytes a* utc
1124 #-----------------------------------------------------------------------------
1125 # asnGetBitString: Extract a Bit String value (a string of 0/1s) from the
1128 #-----------------------------------------------------------------------------
1130 proc asn::asnGetBitString {data_var bitstring_var} {
1131 upvar $data_var data $bitstring_var bitstring
1135 return -code error \
1136 [format "Expected Bit String (0x03), but got %02x" $tag]
1139 asnGetLength data length
1140 # get the number of padding bits used at the end
1141 asnGetByte data padding
1143 asnGetBytes data $length bytes
1144 binary scan $bytes B* bits
1146 # cut off the padding bits
1147 set bits [string range $bits 0 end-$padding]
1151 #-----------------------------------------------------------------------------
1152 # asnGetObjectIdentifier: Decode an ASN.1 Object Identifier (OID) into
1153 # a Tcl list of integers.
1154 #-----------------------------------------------------------------------------
1156 proc asn::asnGetObjectIdentifier {data_var oid_var} {
1157 upvar $data_var data $oid_var oid
1161 return -code error \
1162 [format "Expected Object Identifier (0x06), but got %02x" $tag]
1164 asnGetLength data length
1166 # the first byte encodes the OID parts in position 0 and 1
1168 set oid [expr {$val / 40}]
1169 lappend oid [expr {$val % 40}]
1172 # the next bytes encode the remaining parts of the OID
1176 asnGetByte data octet
1181 foreach byte $bytes {
1183 incr oidval [expr {$mult*$byte}]
1184 set mult [expr {$mult*128}]
1191 set byte [expr {$octet-128}]
1192 set bytes [concat [list $byte] $bytes]
1197 return -code error "OID Data is incomplete, not enough octets."
1202 #-----------------------------------------------------------------------------
1203 # asnGetContext: Decode an explicit context tag
1205 #-----------------------------------------------------------------------------
1207 proc ::asn::asnGetContext {data_var contextNumber_var {content_var {}} {constructed_var {}}} {
1208 upvar 1 $data_var data $contextNumber_var contextNumber
1211 asnGetLength data length
1213 if {($tag & 0xC0) != 0x080} {
1214 return -code error \
1215 [format "Expected Context (0xa0 or 0x80), but got %02x" $tag]
1217 set contextNumber [expr {$tag & 0x1F}]
1218 if {[string length $constructed_var]} {
1219 upvar 1 $constructed_var constructed
1220 set constructed [expr {$tag & 0x20}]
1222 if {[string length $content_var]} {
1223 upvar 1 $content_var content
1224 asnGetBytes data $length content
1230 #-----------------------------------------------------------------------------
1231 # asnGetNumericString: Decode a Numeric String from the data
1232 #-----------------------------------------------------------------------------
1234 proc ::asn::asnGetNumericString {data_var print_var} {
1235 upvar 1 $data_var data $print_var print
1239 return -code error \
1240 [format "Expected Numeric String (0x12), but got %02x" $tag]
1242 asnGetLength data length
1243 asnGetBytes data $length string
1244 set print [encoding convertfrom ascii $string]
1248 #-----------------------------------------------------------------------------
1249 # asnGetPrintableString: Decode a Printable String from the data
1250 #-----------------------------------------------------------------------------
1252 proc ::asn::asnGetPrintableString {data_var print_var} {
1253 upvar $data_var data $print_var print
1257 return -code error \
1258 [format "Expected Printable String (0x13), but got %02x" $tag]
1260 asnGetLength data length
1261 asnGetBytes data $length string
1262 set print [encoding convertfrom ascii $string]
1266 #-----------------------------------------------------------------------------
1267 # asnGetIA5String: Decode a IA5(ASCII) String from the data
1268 #-----------------------------------------------------------------------------
1270 proc ::asn::asnGetIA5String {data_var print_var} {
1271 upvar $data_var data $print_var print
1275 return -code error \
1276 [format "Expected IA5 String (0x16), but got %02x" $tag]
1278 asnGetLength data length
1279 asnGetBytes data $length string
1280 set print [encoding convertfrom ascii $string]
1283 #------------------------------------------------------------------------
1284 # asnGetBMPString: Decode Basic Multiningval (UCS2 string) from data
1285 #------------------------------------------------------------------------
1286 proc asn::asnGetBMPString {data_var print_var} {
1287 upvar $data_var data $print_var print
1290 return -code error \
1291 [format "Expected BMP String (0x1e), but got %02x" $tag]
1293 asnGetLength data length
1294 asnGetBytes data $length string
1295 if {$::tcl_platform(byteOrder) eq "littleEndian"} {
1297 foreach {hi lo} [split $string ""] {
1303 set print [encoding convertfrom unicode $str2]
1306 #------------------------------------------------------------------------
1307 # asnGetUTF8String: Decode UTF8 string from data
1308 #------------------------------------------------------------------------
1309 proc asn::asnGetUTF8String {data_var print_var} {
1310 upvar $data_var data $print_var print
1313 return -code error \
1314 [format "Expected UTF8 String (0x0c), but got %02x" $tag]
1316 asnGetLength data length
1317 asnGetBytes data $length string
1318 #there should be some error checking to see if input is
1319 #properly-formatted utf8
1320 set print [encoding convertfrom utf-8 $string]
1324 #-----------------------------------------------------------------------------
1325 # asnGetNull: decode a NULL value
1326 #-----------------------------------------------------------------------------
1328 proc ::asn::asnGetNull {data_var} {
1329 upvar $data_var data
1333 return -code error \
1334 [format "Expected NULL (0x05), but got %02x" $tag]
1337 asnGetLength data length
1338 asnGetBytes data $length bytes
1340 # we do not check the null data, all bytes must be 0x00
1345 #----------------------------------------------------------------------------
1346 # MultiType string routines
1347 #----------------------------------------------------------------------------
1349 namespace eval asn {
1350 variable stringTypes
1351 array set stringTypes {
1363 variable defaultStringType UTF8
1365 #---------------------------------------------------------------------------
1366 # asnGetString - get readable string automatically detecting its type
1367 #---------------------------------------------------------------------------
1368 proc ::asn::asnGetString {data_var print_var {type_var {}}} {
1369 variable stringTypes
1370 upvar $data_var data $print_var print
1371 asnPeekByte data tag
1372 set tag [format %02x $tag]
1373 if {![info exists stringTypes($tag)]} {
1374 return -code error "Expected one of string types, but got $tag"
1376 asnGet$stringTypes($tag) data print
1377 if {[string length $type_var]} {
1378 upvar $type_var type
1379 set type $stringTypes($tag)
1382 #---------------------------------------------------------------------
1383 # defaultStringType - set or query default type for unrestricted strings
1384 #---------------------------------------------------------------------
1385 proc ::asn::defaultStringType {{type {}}} {
1386 variable defaultStringType
1387 if {![string length $type]} {
1388 return $defaultStringType
1390 if {$type ne "BMP" && $type ne "UTF8"} {
1391 return -code error "Invalid default string type. Should be one of BMP, UTF8"
1393 set defaultStringType $type
1397 #---------------------------------------------------------------------------
1398 # asnString - encode readable string into most restricted type possible
1399 #---------------------------------------------------------------------------
1401 proc ::asn::asnString {string} {
1402 variable nonPrintableChars
1403 variable nonNumericChars
1404 if {[string length $string]!=[string bytelength $string]} {
1405 # There are non-ascii character
1406 variable defaultStringType
1407 return [asn${defaultStringType}String $string]
1408 } elseif {![regexp $nonNumericChars $string]} {
1409 return [asnNumericString $string]
1410 } elseif {![regexp $nonPrintableChars $string]} {
1411 return [asnPrintableString $string]
1413 return [asnIA5String $string]
1417 #-----------------------------------------------------------------------------
1418 package provide asn 0.7.1