00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046 package require Tcl 8.4
00047
00048 namespace asn {
00049
00050 namespace export \
00051 asnSequence \
00052 asnSequenceFromList \
00053 asnSet \
00054 asnSetFromList \
00055 asnApplicationConstr \
00056 asnApplication \
00057 asnContext\
00058 asnContextConstr\
00059 asnChoice \
00060 asnChoiceConstr \
00061 asnInteger \
00062 asnEnumeration \
00063 asnBoolean \
00064 asnOctetString \
00065 asnNull \
00066 asnUTCTime \
00067 asnNumericString \
00068 asnPrintableString \
00069 asnIA5String\
00070 asnBMPString\
00071 asnUTF8String\
00072 asnBitString \
00073 asnObjectIdentifer
00074
00075
00076 namespace export \
00077 asnGetResponse \
00078 asnGetInteger \
00079 asnGetEnumeration \
00080 asnGetOctetString \
00081 asnGetSequence \
00082 asnGetSet \
00083 asnGetApplication \
00084 asnGetNumericString \
00085 asnGetPrintableString \
00086 asnGetIA5String \
00087 asnGetBMPString \
00088 asnGetUTF8String \
00089 asnGetObjectIdentifier \
00090 asnGetBoolean \
00091 asnGetUTCTime \
00092 asnGetBitString \
00093 asnGetContext
00094
00095
00096 namespace export \
00097 asnPeekByte \
00098 asnGetLength \
00099 asnRetag \
00100 asnPeekTag \
00101 asnTag
00102
00103 }
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135 ret ::asn::asnLength (type len) {
00136
00137 if {$len < 0} {
00138 return -code error "Negative length octet requested"
00139 }
00140 if {$len < 128} {
00141 # short form: ISO X.690 8.1.3.4
00142 return [binary format c $len]
00143 }
00144 # long form: ISO X.690 8.1.3.5
00145 # try to use a minimal encoding,
00146 # even if not required by BER, but it is required by DER
00147 # take care for signed vs. unsigned issues
00148 if {$len < 256 } {
00149 return [binary format H2c 81 [expr {$len - 256}]]
00150 }
00151 if {$len < 32769} {
00152 # two octet signed value
00153 return [binary format H2S 82 $len]
00154 }
00155 if {$len < 65536} {
00156 return [binary format H2S 82 [expr {$len - 65536}]]
00157 }
00158 if {$len < 8388608} {
00159 # three octet signed value
00160 return [binary format H2cS 83 [expr {$len >> 16}] [expr {($len & 0xFFFF) - 65536}]]
00161 }
00162 if {$len < 16777216} {
00163 # three octet signed value
00164 return [binary format H2cS 83 [expr {($len >> 16) -256}] [expr {($len & 0xFFFF) -65536}]]
00165 }
00166 if {$len < 2147483649} {
00167 # four octet signed value
00168 return [binary format H2I 84 $len]
00169 }
00170 if {$len < 4294967296} {
00171 # four octet unsigned value
00172 return [binary format H2I 84 [expr {$len - 4294967296}]]
00173 }
00174 if {$len < 1099511627776} {
00175 # five octet unsigned value
00176 return [binary format H2 85][string range [binary format W $len] 3 end]
00177 }
00178 if {$len < 281474976710656} {
00179 # six octet unsigned value
00180 return [binary format H2 86][string range [binary format W $len] 2 end]
00181 }
00182 if {$len < 72057594037927936} {
00183 # seven octet value
00184 return [binary format H2 87][string range [binary format W $len] 1 end]
00185 }
00186
00187 # must be a 64-bit wide signed value
00188 return [binary format H2W 88 $len]
00189 }
00190
00191
00192
00193
00194
00195 ret ::asn::asnSequence (type args) {
00196 asnSequenceFromList $args
00197 }
00198
00199 ret ::asn::asnSequenceFromList (type lst) {
00200 # The sequence tag is 0x30. The length is arbitrary and thus full
00201 # length coding is required. The arguments have to be BER encoded
00202 # already. Constructed value, definite-length encoding.
00203
00204 set out ""
00205 foreach part $lst {
00206 append out $part
00207 }
00208 set len [string length $out]
00209 return [binary format H2a*a$len 30 [asnLength $len] $out]
00210 }
00211
00212
00213
00214
00215
00216
00217 ret ::asn::asnSet (type args) {
00218 asnSetFromList $args
00219 }
00220
00221 ret ::asn::asnSetFromList (type lst) {
00222 # The set tag is 0x31. The length is arbitrary and thus full
00223 # length coding is required. The arguments have to be BER encoded
00224 # already.
00225
00226 set out ""
00227 foreach part $lst {
00228 append out $part
00229 }
00230 set len [string length $out]
00231 return [binary format H2a*a$len 31 [asnLength $len] $out]
00232 }
00233
00234
00235
00236
00237
00238
00239 ret ::asn::asnApplicationConstr (type appNumber , type args) {
00240 # Packs the arguments into a constructed value with application tag.
00241
00242 set out ""
00243 foreach part $args {
00244 append out $part
00245 }
00246 set code [expr {0x060 + $appNumber}]
00247 set len [string length $out]
00248 return [binary format ca*a$len $code [asnLength $len] $out]
00249 }
00250
00251
00252
00253
00254
00255 ret ::asn::asnApplication (type appNumber , type data) {
00256 # Packs the arguments into a constructed value with application tag.
00257
00258 set code [expr {0x040 + $appNumber}]
00259 set len [string length $data]
00260 return [binary format ca*a$len $code [asnLength $len] $data]
00261 }
00262
00263
00264
00265
00266
00267 ret ::asn::asnContextConstr (type contextNumber , type args) {
00268 # Packs the arguments into a constructed value with application tag.
00269
00270 set out ""
00271 foreach part $args {
00272 append out $part
00273 }
00274 set code [expr {0x0A0 + $contextNumber}]
00275 set len [string length $out]
00276 return [binary format ca*a$len $code [asnLength $len] $out]
00277 }
00278
00279
00280
00281
00282
00283 ret ::asn::asnContext (type contextNumber , type data) {
00284 # Packs the arguments into a constructed value with application tag.
00285 set code [expr {0x080 + $contextNumber}]
00286 set len [string length $data]
00287 return [binary format ca*a$len $code [asnLength $len] $data]
00288 }
00289
00290
00291
00292
00293 ret ::asn::asnChoice (type appNumber , type args) {
00294 # Packs the arguments into a choice construction.
00295
00296 set out ""
00297 foreach part $args {
00298 append out $part
00299 }
00300 set code [expr {0x080 + $appNumber}]
00301 set len [string length $out]
00302 return [binary format ca*a$len $code [asnLength $len] $out]
00303 }
00304
00305
00306
00307
00308
00309 ret ::asn::asnChoiceConstr (type appNumber , type args) {
00310 # Packs the arguments into a choice construction.
00311
00312 set out ""
00313 foreach part $args {
00314 append out $part
00315 }
00316 set code [expr {0x0A0 + $appNumber}]
00317 set len [string length $out]
00318 return [binary format ca*a$len $code [asnLength $len] $out]
00319 }
00320
00321
00322
00323
00324
00325 ret ::asn::asnInteger (type number) {
00326 asnIntegerOrEnum 02 $number
00327 }
00328
00329
00330
00331
00332
00333 ret ::asn::asnEnumeration (type number) {
00334 asnIntegerOrEnum 0a $number
00335 }
00336
00337
00338
00339
00340
00341
00342 ret ::asn::asnIntegerOrEnum (type tag , type number) {
00343 # The integer tag is 0x02 , the Enum Tag 0x0a otherwise identical.
00344 # The length is 1, 2, 3, or 4, coded in a
00345 # single byte. This can be done directly, no need to go through
00346 # asnLength. The value itself is written in big-endian.
00347
00348 # Known bug/issue: The command cannot handle very wide integers, i.e.
00349 # anything above 8 bytes length. Use asnBignumInteger for those.
00350
00351 # check if we really have an int
00352 set num $number
00353 incr num
00354
00355 if {($number >= -128) && ($number < 128)} {
00356 return [binary format H2H2c $tag 01 $number]
00357 }
00358 if {($number >= -32768) && ($number < 32768)} {
00359 return [binary format H2H2S $tag 02 $number]
00360 }
00361 if {($number >= -8388608) && ($number < 8388608)} {
00362 set numberb [expr {$number & 0xFFFF}]
00363 set numbera [expr {($number >> 16) & 0xFF}]
00364 return [binary format H2H2cS $tag 03 $numbera $numberb]
00365 }
00366 if {($number >= -2147483648) && ($number < 2147483648)} {
00367 return [binary format H2H2I $tag 04 $number]
00368 }
00369 if {($number >= -549755813888) && ($number < 549755813888)} {
00370 set numberb [expr {$number & 0xFFFFFFFF}]
00371 set numbera [expr {($number >> 32) & 0xFF}]
00372 return [binary format H2H2cI $tag 05 $numbera $numberb]
00373 }
00374 if {($number >= -140737488355328) && ($number < 140737488355328)} {
00375 set numberb [expr {$number & 0xFFFFFFFF}]
00376 set numbera [expr {($number >> 32) & 0xFFFF}]
00377 return [binary format H2H2SI $tag 06 $numbera $numberb]
00378 }
00379 if {($number >= -36028797018963968) && ($number < 36028797018963968)} {
00380 set numberc [expr {$number & 0xFFFFFFFF}]
00381 set numberb [expr {($number >> 32) & 0xFFFF}]
00382 set numbera [expr {($number >> 48) & 0xFF}]
00383 return [binary format H2H2cSI $tag 07 $numbera $numberb $numberc]
00384 }
00385 if {($number >= -9223372036854775808) && ($number <= 9223372036854775807)} {
00386 return [binary format H2H2W $tag 08 $number]
00387 }
00388 return -code error "Integer value to large to encode, use asnBigInteger"
00389 }
00390
00391
00392
00393
00394
00395 ret ::asn::asnBigInteger (type bignum) {
00396 # require math::bignum only if it is used
00397 package require math::bignum
00398
00399 # this is a hack to check for bignum...
00400 if {[llength $bignum] < 2 || ([lindex $bignum 0] ne "bignum")} {
00401 return -code error "expected math::bignum value got \"$bignum\""
00402 }
00403 if {[math::bignum::sign $bignum]} {
00404 # generate two's complement form
00405 set bits [math::bignum::bits $bignum]
00406 set padding [expr {$bits % 8}]
00407 set len [expr {int(ceil($bits / 8.0))}]
00408 if {$padding == 0} {
00409 # we need a complete extra byte for the sign
00410 # unless this is a base 2 multiple
00411 set test [math::bignum::fromstr 0]
00412 math::bignum::setbit test [expr {$bits-1}]
00413 if {[math::bignum::ne [math::bignum::abs $bignum] $test]} {
00414 incr len
00415 }
00416 }
00417 set exp [math::bignum::pow \
00418 [math::bignum::fromstr 256] \
00419 [math::bignum::fromstr $len]]
00420 set bignum [math::bignum::add $bignum $exp]
00421 set hex [math::bignum::tostr $bignum 16]
00422 } else {
00423 set bits [math::bignum::bits $bignum]
00424 if {($bits % 8) == 0 && $bits > 0} {
00425 set pad "00"
00426 } else {
00427 set pad ""
00428 }
00429 set hex $pad[math::bignum::tostr $bignum 16]
00430 }
00431 if {[string length $hex]%2} {
00432 set hex "0$hex"
00433 }
00434 set octets [expr {(([string length $hex]+1)/2)}]
00435 return [binary format H2a*H* 02 [asnLength $octets] $hex]
00436 }
00437
00438
00439
00440
00441
00442
00443 ret ::asn::asnBoolean (type bool) {
00444 # The boolean tag is 0x01. The length is always 1, coded in
00445 # a single byte. This can be done directly, no need to go through
00446 # asnLength. The value itself is written in big-endian.
00447
00448 return [binary format H2H2c 01 01 [expr {$bool ? 0x0FF : 0x0}]]
00449 }
00450
00451
00452
00453
00454
00455 ret ::asn::asnOctetString (type string) {
00456 # The octet tag is 0x04. The length is arbitrary, so we need
00457 # 'asnLength' for full coding of the length.
00458
00459 set len [string length $string]
00460 return [binary format H2a*a$len 04 [asnLength $len] $string]
00461 }
00462
00463
00464
00465
00466
00467 ret ::asn::asnNull () {
00468 # Null has only one valid encoding
00469 return \x05\x00
00470 }
00471
00472
00473
00474
00475
00476 ret ::asn::asnBitString (type bitstring) {
00477 # The bit string tag is 0x03.
00478 # Bit strings can be either simple or constructed
00479 # we always use simple encoding
00480
00481 set bitlen [string length $bitstring]
00482 set padding [expr {(8 - ($bitlen % 8)) % 8}]
00483 set len [expr {($bitlen / 8) + 1}]
00484 if {$padding != 0} {incr len}
00485
00486 return [binary format H2a*B* 03 [asnLength $len] $bitstring]
00487 }
00488
00489
00490
00491
00492
00493 ret ::asn::asnUTCTime (type UTCtimestring) {
00494 # the utc time tag is 0x17.
00495 #
00496 # BUG: we do not check the string for well formedness
00497
00498 set ascii [encoding convertto ascii $UTCtimestring]
00499 set len [string length $ascii]
00500 return [binary format H2a*a* 17 [asnLength $len] $ascii]
00501 }
00502
00503
00504
00505
00506 namespace asn {
00507 variable nonPrintableChars {[^ A-Za-z0-9'()+,.:/?=-]}
00508 }
00509 ret ::asn::asnPrintableString (type string) {
00510 # the printable string tag is 0x13
00511 variable nonPrintableChars
00512 # it is basically a restricted ascii string
00513 if {[regexp $nonPrintableChars $string ]} {
00514 return -code error "Illegal character in PrintableString."
00515 }
00516
00517 # check characters
00518 set ascii [encoding convertto ascii $string]
00519 return [asnEncodeString 13 $ascii]
00520 }
00521
00522
00523
00524
00525 ret ::asn::asnIA5String (type string) {
00526 # the IA5 string tag is 0x16
00527 # check for extended charachers
00528 if {[string length $string]!=[string bytelength $string]} {
00529 return -code error "Illegal character in IA5String"
00530 }
00531 set ascii [encoding convertto ascii $string]
00532 return [asnEncodeString 16 $ascii]
00533 }
00534
00535
00536
00537
00538 namespace asn {
00539 variable nonNumericChars {[^0-9 ]}
00540 }
00541 ret ::asn::asnNumericString (type string) {
00542 # the Numeric String type has tag 0x12
00543 variable nonNumericChars
00544 if {[regexp $nonNumericChars $string]} {
00545 return -code error "Illegal character in Numeric String."
00546 }
00547
00548 return [asnEncodeString 12 $string]
00549 }
00550
00551
00552
00553 ret asn::asnBMPString (type string) {
00554 if {$::tcl_platform(byteOrder) eq "littleEndian"} {
00555 set bytes ""
00556 foreach {lo hi} [split [encoding convertto unicode $string] ""] {
00557 append bytes $hi $lo
00558 }
00559 } else {
00560 set bytes [encoding convertto unicode $string]
00561 }
00562 return [asnEncodeString 1e $bytes]
00563 }
00564
00565
00566
00567 ret asn::asnUTF8String (type string) {
00568 return [asnEncodeString 0c [encoding convertto utf-8 $string]]
00569 }
00570
00571
00572
00573 ret ::asn::asnEncodeString (type tag , type string) {
00574 set len [string length $string]
00575 return [binary format H2a*a$len $tag [asnLength $len] $string]
00576 }
00577
00578
00579
00580
00581 ret ::asn::asnObjectIdentifier (type oid) {
00582 # the object identifier tag is 0x06
00583
00584 if {[llength $oid] < 2} {
00585 return -code error "OID must have at least two subidentifiers."
00586 }
00587
00588 # basic check that it is valid
00589 foreach identifier $oid {
00590 if {$identifier < 0} {
00591 return -code error \
00592 "Malformed OID. Identifiers must be positive Integers."
00593 }
00594 }
00595
00596 if {[lindex $oid 0] > 2} {
00597 return -code error "First subidentifier must be 0,1 or 2"
00598 }
00599 if {[lindex $oid 1] > 39} {
00600 return -code error \
00601 "Second subidentifier must be between 0 and 39"
00602 }
00603
00604 # handle the special cases directly
00605 switch [llength $oid] {
00606 2 { return [binary format H2H2c 06 01 \
00607 [expr {[lindex $oid 0]*40+[lindex $oid 1]}]] }
00608 default {
00609 # This can probably be written much shorter.
00610 # Just a first try that works...
00611 #
00612 set octets [binary format c \
00613 [expr {[lindex $oid 0]*40+[lindex $oid 1]}]]
00614 foreach identifier [lrange $oid 2 end] {
00615 set d 128
00616 if {$identifier < 128} {
00617 set subidentifier [list $identifier]
00618 } else {
00619 set subidentifier [list]
00620 # find the largest divisor
00621
00622 while {($identifier / $d) >= 128} {
00623 set d [expr {$d * 128}]
00624 }
00625 # and construct the subidentifiers
00626 set remainder $identifier
00627 while {$d >= 128} {
00628 set coefficient [expr {($remainder / $d) | 0x80}]
00629 set remainder [expr {$remainder % $d}]
00630 set d [expr {$d / 128}]
00631 lappend subidentifier $coefficient
00632 }
00633 lappend subidentifier $remainder
00634 }
00635 append octets [binary format c* $subidentifier]
00636 }
00637 return [binary format H2a*a* 06 \
00638 [asnLength [string length $octets]] $octets]
00639 }
00640 }
00641
00642 }
00643
00644
00645
00646
00647
00648 ret ::asn::asnGetResponse (type sock , type data_, type var) {
00649 upvar 1 $data_var data
00650
00651 # We expect a sequence here (tag 0x30). The code below is an
00652 # inlined replica of 'asnGetSequence', modified for reading from a
00653 # channel instead of a string.
00654
00655 set tag [read $sock 1]
00656
00657 if {$tag == "\x30"} {
00658 # The following code is a replica of 'asnGetLength', modified
00659 # for reading the bytes from the channel instead of a string.
00660
00661 set len1 [read $sock 1]
00662 binary scan $len1 c num
00663 set length [expr {($num + 0x100) % 0x100}]
00664
00665 if {$length >= 0x080} {
00666 # The byte the read is not the length, but a prefix, and
00667 # the lower nibble tells us how many bytes follow.
00668
00669 set len_length [expr {$length & 0x7f}]
00670
00671 # BUG: We should not perform the value extraction for an
00672 # BUG: improper length. It wastes cycles, and here it can
00673 # BUG: cause us trouble, reading more data than there is
00674 # BUG: on the channel. Depending on the channel
00675 # BUG: configuration an attacker can induce us to block,
00676 # BUG: causing a denial of service.
00677 set lengthBytes [read $sock $len_length]
00678
00679 switch $len_length {
00680 1 {
00681 binary scan $lengthBytes c length
00682 set length [expr {($length + 0x100) % 0x100}]
00683 }
00684 2 { binary scan $lengthBytes S length }
00685 3 { binary scan \x00$lengthBytes I length }
00686 4 { binary scan $lengthBytes I length }
00687 default {
00688 return -code error \
00689 "length information too long ($len_length)"
00690 }
00691 }
00692 }
00693
00694 # Now that the length is known we get the remainder,
00695 # i.e. payload, and construct proper in-memory BER encoded
00696 # sequence.
00697
00698 set rest [read $sock $length]
00699 set data [binary format aa*a$length $tag [asnLength $length] $rest]
00700 } else {
00701 # Generate an error message if the data is not a sequence as
00702 # we expected.
00703
00704 set tag_hex ""
00705 binary scan $tag H2 tag_hex
00706 return -code error "unknown start tag [string length $tag] $tag_hex"
00707 }
00708 }
00709
00710
00711
00712
00713
00714 ret ::asn::asnGetByte (type data_, type var , type byte_, type var) {
00715 upvar $data_var data $byte_var byte
00716
00717 binary scan [string index $data 0] c byte
00718 set byte [expr {($byte + 0x100) % 0x100}]
00719 set data [string range $data 1 end]
00720
00721 return
00722 }
00723
00724
00725
00726
00727
00728
00729 ret ::asn::asnPeekByte (type data_, type var , type byte_, type var , optional offset =0) {
00730 upvar 1 $data_var data $byte_var byte
00731
00732 binary scan [string index $data $offset] c byte
00733 set byte [expr {($byte + 0x100) % 0x100}]
00734
00735 return
00736 }
00737
00738
00739
00740
00741
00742 ret ::asn::asnRetag (type data_, type var , type newTag) {
00743 upvar 1 $data_var data
00744 set tag ""
00745 set type ""
00746 set len [asnPeekTag data tag type dummy]
00747 asnGetBytes data $len tagbytes
00748 set data [binary format c* $newTag]$data
00749 }
00750
00751
00752
00753
00754
00755 ret ::asn::asnGetBytes (type data_, type var , type length , type bytes_, type var) {
00756 upvar 1 $data_var data $bytes_var bytes
00757
00758 incr length -1
00759 set bytes [string range $data 0 $length]
00760 incr length
00761 set data [string range $data $length end]
00762
00763 return
00764 }
00765
00766
00767
00768
00769
00770 ret ::asn::asnPeekTag (type data_, type var , type tag_, type var , type tagtype_, type var , type constr_, type var) {
00771 upvar 1 $data_var data $tag_var tag $tagtype_var tagtype $constr_var constr
00772
00773 set type 0
00774 set offset 0
00775 asnPeekByte data type $offset
00776 # check if we have a simple tag, < 31, which fits in one byte
00777
00778 set tval [expr {$type & 0x1f}]
00779 if {$tval == 0x1f} {
00780 # long tag, max 64-bit with Tcl 8.4, unlimited with 8.5 bignum
00781 asnPeekByte data tagbyte [incr offset]
00782 set tval [expr {wide($tagbyte & 0x7f)}]
00783 while {($tagbyte & 0x80)} {
00784 asnPeekByte data tagbyte [incr offset]
00785 set tval [expr {($tval << 7) + ($tagbyte & 0x7f)}]
00786 }
00787 }
00788
00789 set tagtype [lindex {UNIVERSAL APPLICATION CONTEXT PRIVATE} \
00790 [expr {($type & 0xc0) >>6}]]
00791 set tag $tval
00792 set constr [expr {($type & 0x20) > 0}]
00793
00794 return [incr offset]
00795 }
00796
00797
00798
00799
00800
00801 ret ::asn::asnTag (type tagnumber , optional class =UNIVERSAL , optional tagstyle =P) {
00802 set first 0
00803 if {$tagnumber < 31} {
00804 # encode everything in one byte
00805 set first $tagnumber
00806 set bytes [list]
00807 } else {
00808 # multi-byte tag
00809 set first 31
00810 set bytes [list [expr {$tagnumber & 0x7f}]]
00811 set tagnumber [expr {$tagnumber >> 7}]
00812 while {$tagnumber > 0} {
00813 lappend bytes [expr {($tagnumber & 0x7f)+0x80}]
00814 set tagnumber [expr {$tagnumber >>7}]
00815 }
00816
00817 }
00818
00819 if {$tagstyle eq "C" || $tagstyle == 1 } {incr first 32}
00820 switch -glob -- $class {
00821 U* { ;# UNIVERSAL }
00822 A* { incr first 64 ;# APPLICATION }
00823 C* { incr first 128 ;# CONTEXT }
00824 P* { incr first 192 ;# PRIVATE }
00825 default {
00826 return -code error "Unknown tag class \"$class\""
00827 }
00828 }
00829 if {[llength $bytes] > 0} {
00830 # long tag
00831 set rbytes [list]
00832 for {set i [expr {[llength $bytes]-1}]} {$i >= 0} {incr i -1} {
00833 lappend rbytes [lindex $bytes $i]
00834 }
00835 return [binary format cc* $first $rbytes ]
00836 }
00837 return [binary format c $first]
00838 }
00839
00840
00841
00842
00843
00844 ret ::asn::asnGetLength (type data_, type var , type length_, type var) {
00845 upvar 1 $data_var data $length_var length
00846
00847 asnGetByte data length
00848 if {$length == 0x080} {
00849 return -code error "Indefinite length BER encoding not yet supported"
00850 }
00851 if {$length > 0x080} {
00852 # The retrieved byte is a prefix value, and the integer in the
00853 # lower nibble tells us how many bytes were used to encode the
00854 # length data following immediately after this prefix.
00855
00856 set len_length [expr {$length & 0x7f}]
00857
00858 if {[string length $data] < $len_length} {
00859 return -code error \
00860 "length information invalid, not enough octets left"
00861 }
00862
00863 asnGetBytes data $len_length lengthBytes
00864
00865 switch $len_length {
00866 1 {
00867 # Efficiently coded data will not go through this
00868 # path, as small length values can be coded directly,
00869 # without a prefix.
00870
00871 binary scan $lengthBytes c length
00872 set length [expr {($length + 0x100) % 0x100}]
00873 }
00874 2 { binary scan $lengthBytes S length
00875 set length [expr {($length + 0x10000) % 0x10000}]
00876 }
00877 3 { binary scan \x00$lengthBytes I length
00878 set length [expr {($length + 0x1000000) % 0x1000000}]
00879 }
00880 4 { binary scan $lengthBytes I length
00881 set length [expr {(wide($length) + 0x100000000) % 0x100000000}]
00882 }
00883 default {
00884 binary scan $lengthBytes H* hexstr
00885 # skip leading zeros which are allowed by BER
00886 set hexlen [string trimleft $hexstr 0]
00887 # check if it fits into a 64-bit signed integer
00888 if {[string length $hexlen] > 16} {
00889 return -code error -errorcode {ARITH IOVERFLOW
00890 {Length value too large for normal use, try asnGetBigLength}} \
00891 "Length value to large"
00892 } elseif { [string length $hexlen] == 16 \
00893 && ([string index $hexlen 0] & 0x8)} {
00894 # check most significant bit, if set we need bignum
00895 return -code error -errorcode {ARITH IOVERFLOW
00896 {Length value too large for normal use, try asnGetBigLength}} \
00897 "Length value to large"
00898 } else {
00899 scan $hexstr "%lx" length
00900 }
00901 }
00902 }
00903 }
00904 return
00905 }
00906
00907
00908
00909
00910
00911
00912 ret ::asn::asnGetBigLength (type data_, type var , type biglength_, type var) {
00913
00914 # Does any real world code really need this?
00915 # If we encounter this, we are doomed to fail anyway,
00916 # (there would be an Exabyte inside the data_var, )
00917 #
00918 # So i implement it just for completness.
00919 #
00920 package require math::bignum
00921
00922 upvar 1 $data_var data $biglength_var length
00923
00924 asnGetByte data length
00925 if {$length == 0x080} {
00926 return -code error "Indefinite length BER encoding not yet supported"
00927 }
00928 if {$length > 0x080} {
00929 # The retrieved byte is a prefix value, and the integer in the
00930 # lower nibble tells us how many bytes were used to encode the
00931 # length data following immediately after this prefix.
00932
00933 set len_length [expr {$length & 0x7f}]
00934
00935 if {[string length $data] < $len_length} {
00936 return -code error \
00937 "length information invalid, not enough octets left"
00938 }
00939
00940 asnGetBytes data $len_length lengthBytes
00941 binary scan $lengthBytes H* hexlen
00942 set length [math::bignum::fromstr $hexlen 16]
00943 }
00944 return
00945 }
00946
00947
00948
00949
00950
00951 ret ::asn::asnGetInteger (type data_, type var , type int_, type var) {
00952 # Tag is 0x02.
00953
00954 upvar 1 $data_var data $int_var int
00955
00956 asnGetByte data tag
00957
00958 if {$tag != 0x02} {
00959 return -code error \
00960 [format "Expected Integer (0x02), but got %02x" $tag]
00961 }
00962
00963 asnGetLength data len
00964 asnGetBytes data $len integerBytes
00965
00966 set int ?
00967
00968 switch $len {
00969 1 { binary scan $integerBytes c int }
00970 2 { binary scan $integerBytes S int }
00971 3 {
00972 # check for negative int and pad
00973 scan [string index $integerBytes 0] %c byte
00974 if {$byte & 128} {
00975 binary scan \xff$integerBytes I int
00976 } else {
00977 binary scan \x00$integerBytes I int
00978 }
00979 }
00980 4 { binary scan $integerBytes I int }
00981 5 -
00982 6 -
00983 7 -
00984 8 {
00985 # check for negative int and pad
00986 scan [string index $integerBytes 0] %c byte
00987 if {$byte & 128} {
00988 set pad [string repeat \xff [expr {8-$len}]]
00989 } else {
00990 set pad [string repeat \x00 [expr {8-$len}]]
00991 }
00992 binary scan $pad$integerBytes W int
00993 }
00994 default {
00995 # Too long, or prefix coding was used.
00996 return -code error "length information too long"
00997 }
00998 }
00999 return
01000 }
01001
01002
01003
01004
01005
01006 ret ::asn::asnGetBigInteger (type data_, type var , type bignum_, type var) {
01007 # require math::bignum only if it is used
01008 package require math::bignum
01009
01010 # Tag is 0x02. We expect that the length of the integer is coded with
01011 # maximal efficiency, i.e. without a prefix 0x81 prefix. If a prefix
01012 # is used this decoder will fail.
01013
01014 upvar 1 $data_var data $bignum_var bignum
01015
01016 asnGetByte data tag
01017
01018 if {$tag != 0x02} {
01019 return -code error \
01020 [format "Expected Integer (0x02), but got %02x" $tag]
01021 }
01022
01023 asnGetLength data len
01024 asnGetBytes data $len integerBytes
01025
01026 binary scan $integerBytes H* hex
01027 set bignum [math::bignum::fromstr $hex 16]
01028 set bits [math::bignum::bits $bignum]
01029 set exp [math::bignum::pow \
01030 [math::bignum::fromstr 2] \
01031 [math::bignum::fromstr $bits]]
01032 set big [math::bignum::sub $bignum $exp]
01033 set bignum $big
01034
01035 return
01036 }
01037
01038
01039
01040
01041
01042
01043
01044 ret ::asn::asnGetEnumeration (type data_, type var , type enum_, type var) {
01045 # This is like 'asnGetInteger', except for a different tag.
01046
01047 upvar 1 $data_var data $enum_var enum
01048
01049 asnGetByte data tag
01050
01051 if {$tag != 0x0a} {
01052 return -code error \
01053 [format "Expected Enumeration (0x0a), but got %02x" $tag]
01054 }
01055
01056 asnGetLength data len
01057 asnGetBytes data $len integerBytes
01058 set enum ?
01059
01060 switch $len {
01061 1 { binary scan $integerBytes c enum }
01062 2 { binary scan $integerBytes S enum }
01063 3 { binary scan \x00$integerBytes I enum }
01064 4 { binary scan $integerBytes I enum }
01065 default {
01066 return -code error "length information too long"
01067 }
01068 }
01069 return
01070 }
01071
01072
01073
01074
01075
01076 ret ::asn::asnGetOctetString (type data_, type var , type string_, type var) {
01077 # Here we need the full decoder for length data.
01078
01079 upvar 1 $data_var data $string_var string
01080
01081 asnGetByte data tag
01082 if {$tag != 0x04} {
01083 return -code error \
01084 [format "Expected Octet String (0x04), but got %02x" $tag]
01085 }
01086 asnGetLength data length
01087 asnGetBytes data $length temp
01088 set string $temp
01089 return
01090 }
01091
01092
01093
01094
01095
01096 ret ::asn::asnGetSequence (type data_, type var , type sequence_, type var) {
01097 # Here we need the full decoder for length data.
01098
01099 upvar 1 $data_var data $sequence_var sequence
01100
01101 asnGetByte data tag
01102 if {$tag != 0x030} {
01103 return -code error \
01104 [format "Expected Sequence (0x30), but got %02x" $tag]
01105 }
01106 asnGetLength data length
01107 asnGetBytes data $length temp
01108 set sequence $temp
01109 return
01110 }
01111
01112
01113
01114
01115
01116 ret ::asn::asnGetSet (type data_, type var , type set_, type var) {
01117 # Here we need the full decoder for length data.
01118
01119 upvar 1 $data_var data $set_var set
01120
01121 asnGetByte data tag
01122 if {$tag != 0x031} {
01123 return -code error \
01124 [format "Expected Set (0x31), but got %02x" $tag]
01125 }
01126 asnGetLength data length
01127 asnGetBytes data $length temp
01128 set set $temp
01129 return
01130 }
01131
01132
01133
01134
01135
01136 ret ::asn::asnGetApplication (type data_, type var , type appNumber_, type var , optional content_var ={) {encodingType_var {}} } {
01137 upvar 1 $data_var data $appNumber_var appNumber
01138
01139 asnGetByte data tag
01140 asnGetLength data length
01141
01142 if {($tag & 0xC0) != 0x40} {
01143 return -code error \
01144 [format "Expected Application, but got %02x" $tag]
01145 }
01146 if {$encodingType_var != {}} {
01147 upvar 1 $encodingType_var encodingType
01148 encodingType = [expr {($tag & 0x20) > 0}]
01149 }
01150 appNumber = [expr {$tag & 0x1F}]
01151 if {[string length $content_var]} {
01152 upvar 1 $content_var content
01153 asnGetBytes data $length content
01154 }
01155 return
01156 }
01157
01158
01159
01160
01161
01162 ret asn::asnGetBoolean (type data_, type var , type bool_, type var) {
01163 upvar 1 $data_var data $bool_var bool
01164
01165 asnGetByte data tag
01166 if {$tag != 0x01} {
01167 return -code error \
01168 [format "Expected Boolean (0x01), but got %02x" $tag]
01169 }
01170
01171 asnGetLength data length
01172 asnGetByte data byte
01173 set bool [expr {$byte == 0 ? 0 : 1}]
01174 return
01175 }
01176
01177
01178
01179
01180
01181
01182
01183 ret asn::asnGetUTCTime (type data_, type var , type utc_, type var) {
01184 upvar 1 $data_var data $utc_var utc
01185
01186 asnGetByte data tag
01187 if {$tag != 0x17} {
01188 return -code error \
01189 [format "Expected UTCTime (0x17), but got %02x" $tag]
01190 }
01191
01192 asnGetLength data length
01193 asnGetBytes data $length bytes
01194
01195 # this should be ascii, make it explicit
01196 set bytes [encoding convertfrom ascii $bytes]
01197 binary scan $bytes a* utc
01198
01199 return
01200 }
01201
01202
01203
01204
01205
01206
01207
01208
01209 ret asn::asnGetBitString (type data_, type var , type bitstring_, type var) {
01210 upvar 1 $data_var data $bitstring_var bitstring
01211
01212 asnGetByte data tag
01213 if {$tag != 0x03} {
01214 return -code error \
01215 [format "Expected Bit String (0x03), but got %02x" $tag]
01216 }
01217
01218 asnGetLength data length
01219 # get the number of padding bits used at the end
01220 asnGetByte data padding
01221 incr length -1
01222 asnGetBytes data $length bytes
01223 binary scan $bytes B* bits
01224
01225 # cut off the padding bits
01226 set bits [string range $bits 0 end-$padding]
01227 set bitstring $bits
01228 }
01229
01230
01231
01232
01233
01234
01235 ret asn::asnGetObjectIdentifier (type data_, type var , type oid_, type var) {
01236 upvar 1 $data_var data $oid_var oid
01237
01238 asnGetByte data tag
01239 if {$tag != 0x06} {
01240 return -code error \
01241 [format "Expected Object Identifier (0x06), but got %02x" $tag]
01242 }
01243 asnGetLength data length
01244
01245 # the first byte encodes the OID parts in position 0 and 1
01246 asnGetByte data val
01247 set oid [expr {$val / 40}]
01248 lappend oid [expr {$val % 40}]
01249 incr length -1
01250
01251 # the next bytes encode the remaining parts of the OID
01252 set bytes [list]
01253 set incomplete 0
01254 while {$length} {
01255 asnGetByte data octet
01256 incr length -1
01257 if {$octet < 128} {
01258 set oidval $octet
01259 set mult 128
01260 foreach byte $bytes {
01261 if {$byte != {}} {
01262 incr oidval [expr {$mult*$byte}]
01263 set mult [expr {$mult*128}]
01264 }
01265 }
01266 lappend oid $oidval
01267 set bytes [list]
01268 set incomplete 0
01269 } else {
01270 set byte [expr {$octet-128}]
01271 set bytes [concat [list $byte] $bytes]
01272 set incomplete 1
01273 }
01274 }
01275 if {$incomplete} {
01276 return -code error "OID Data is incomplete, not enough octets."
01277 }
01278 return
01279 }
01280
01281
01282
01283
01284
01285
01286 ret ::asn::asnGetContext (type data_, type var , type contextNumber_, type var , optional content_var ={) {encodingType_var {}}} {
01287 upvar 1 $data_var data $contextNumber_var contextNumber
01288
01289 asnGetByte data tag
01290 asnGetLength data length
01291
01292 if {($tag & 0xC0) != 0x80} {
01293 return -code error \
01294 [format "Expected Context, but got %02x" $tag]
01295 }
01296 if {$encodingType_var != {}} {
01297 upvar 1 $encodingType_var encodingType
01298 encodingType = [expr {($tag & 0x20) > 0}]
01299 }
01300 contextNumber = [expr {$tag & 0x1F}]
01301 if {[string length $content_var]} {
01302 upvar 1 $content_var content
01303 asnGetBytes data $length content
01304 }
01305 return
01306 }
01307
01308
01309
01310
01311
01312
01313 ret ::asn::asnGetNumericString (type data_, type var , type print_, type var) {
01314 upvar 1 $data_var data $print_var print
01315
01316 asnGetByte data tag
01317 if {$tag != 0x12} {
01318 return -code error \
01319 [format "Expected Numeric String (0x12), but got %02x" $tag]
01320 }
01321 asnGetLength data length
01322 asnGetBytes data $length string
01323 set print [encoding convertfrom ascii $string]
01324 return
01325 }
01326
01327
01328
01329
01330
01331 ret ::asn::asnGetPrintableString (type data_, type var , type print_, type var) {
01332 upvar 1 $data_var data $print_var print
01333
01334 asnGetByte data tag
01335 if {$tag != 0x13} {
01336 return -code error \
01337 [format "Expected Printable String (0x13), but got %02x" $tag]
01338 }
01339 asnGetLength data length
01340 asnGetBytes data $length string
01341 set print [encoding convertfrom ascii $string]
01342 return
01343 }
01344
01345
01346
01347
01348
01349 ret ::asn::asnGetIA5String (type data_, type var , type print_, type var) {
01350 upvar 1 $data_var data $print_var print
01351
01352 asnGetByte data tag
01353 if {$tag != 0x16} {
01354 return -code error \
01355 [format "Expected IA5 String (0x16), but got %02x" $tag]
01356 }
01357 asnGetLength data length
01358 asnGetBytes data $length string
01359 set print [encoding convertfrom ascii $string]
01360 return
01361 }
01362
01363
01364
01365 ret asn::asnGetBMPString (type data_, type var , type print_, type var) {
01366 upvar 1 $data_var data $print_var print
01367 asnGetByte data tag
01368 if {$tag != 0x1e} {
01369 return -code error \
01370 [format "Expected BMP String (0x1e), but got %02x" $tag]
01371 }
01372 asnGetLength data length
01373 asnGetBytes data $length string
01374 if {$::tcl_platform(byteOrder) eq "littleEndian"} {
01375 set str2 ""
01376 foreach {hi lo} [split $string ""] {
01377 append str2 $lo $hi
01378 }
01379 } else {
01380 set str2 $string
01381 }
01382 set print [encoding convertfrom unicode $str2]
01383 return
01384 }
01385
01386
01387
01388 ret asn::asnGetUTF8String (type data_, type var , type print_, type var) {
01389 upvar 1 $data_var data $print_var print
01390 asnGetByte data tag
01391 if {$tag != 0x0c} {
01392 return -code error \
01393 [format "Expected UTF8 String (0x0c), but got %02x" $tag]
01394 }
01395 asnGetLength data length
01396 asnGetBytes data $length string
01397 #there should be some error checking to see if input is
01398 #properly-formatted utf8
01399 set print [encoding convertfrom utf-8 $string]
01400
01401 return
01402 }
01403
01404
01405
01406
01407 ret ::asn::asnGetNull (type data_, type var) {
01408 upvar 1 $data_var data
01409
01410 asnGetByte data tag
01411 if {$tag != 0x05} {
01412 return -code error \
01413 [format "Expected NULL (0x05), but got %02x" $tag]
01414 }
01415
01416 asnGetLength data length
01417 asnGetBytes data $length bytes
01418
01419 # we do not check the null data, all bytes must be 0x00
01420
01421 return
01422 }
01423
01424
01425
01426
01427
01428 namespace asn {
01429 variable stringTypes
01430 array stringTypes = {
01431 12 NumericString
01432 13 PrintableString
01433 16 IA5String
01434 1e BMPString
01435 0c UTF8String
01436 14 T61String
01437 15 VideotexString
01438 1a VisibleString
01439 1b GeneralString
01440 1c UniversalString
01441 }
01442 variable defaultStringType UTF8
01443 }
01444
01445
01446
01447 ret ::asn::asnGetString (type data_, type var , type print_, type var , optional type_var ={)} {
01448 variable stringTypes
01449 upvar 1 $data_var data $print_var print
01450 asnPeekByte data tag
01451 set tag [format %02x $tag]
01452 if {![info exists stringTypes($tag)]} {
01453 return -code error "Expected one of string types, but got $tag"
01454 }
01455 asnGet$stringTypes($tag) data print
01456 if {[string length $type_var]} {
01457 upvar $type_var type
01458 type = $stringTypes($tag)
01459 }
01460 }
01461
01462
01463
01464 ret ::asn::defaultStringType (optional type ={)} {
01465 variable defaultStringType
01466 if {![string length $type]} {
01467 return $defaultStringType
01468 }
01469 if {$type ne "BMP" && $type ne "UTF8"} {
01470 return -code error "Invalid default string type. Should be one of BMP, UTF8"
01471 }
01472 defaultStringType = $type
01473 return
01474 }
01475
01476
01477
01478
01479
01480 ret ::asn::asnString (type string) {
01481 variable nonPrintableChars
01482 variable nonNumericChars
01483 if {[string length $string]!=[string bytelength $string]} {
01484 # There are non-ascii character
01485 variable defaultStringType
01486 return [asn${defaultStringType}String $string]
01487 } elseif {![regexp $nonNumericChars $string]} {
01488 return [asnNumericString $string]
01489 } elseif {![regexp $nonPrintableChars $string]} {
01490 return [asnPrintableString $string]
01491 } else {
01492 return [asnIA5String $string]
01493 }
01494 }
01495
01496
01497 package provide asn 0.8.1
01498
01499