implement ASN1; include "sys.m"; sys: Sys; include "asn1.m"; # Masks TAG_MASK : con 16r1F; CONSTR_MASK : con 16r20; CLASS_MASK : con 16rC0; # Decoding errors OK, ESHORT, ETOOBIG, EVALLEN, ECONSTR, EPRIM, EINVAL, EUNIMPL: con iota; debug : con 0; init() { sys = load Sys Sys->PATH; } # Decode the whole array as a BER encoding of an ASN1 type. # If there's an error, the return string will contain the error. # Depending on the error, the returned elem may or may not # be nil. decode(a: array of byte) : (string, ref Elem) { (ecode, i, elem) := ber_decode(a, 0, len a); return (errstr(ecode, i, len a), elem); } # Like decode, but continue decoding after first element # of array ends. decode_seq(a: array of byte) : (string, list of ref Elem) { (ecode, i, elist) := seq_decode(a, 0, len a, -1, 1); return (errstr(ecode, i, len a), elist); } # Decode the whole array as a BER encoding of an ASN1 value, # (i.e., the part after the tag and length). # Assume the value is encoded as universal tag "kind". # The constr arg is 1 if the value is constructed, 0 if primitive. # If there's an error, the return string will contain the error. # Depending on the error, the returned value may or may not # be nil. decode_value(a: array of byte, kind, constr: int) : (string, ref Value) { n := len a; (ecode, i, val) := value_decode(a, 0, n, n, kind, constr); return (errstr(ecode, i, len a), val); } # The rest of the decoding routines take the array (a), the # starting position (i), and the ending position +1 (n). # They return (err code, new i, [... varies]). # for debugging ber_ind := ""; ber_ind_save := ""; # Decode an ASN1 (tag, length, value). ber_decode(a: array of byte, i, n: int) : (int, int, ref Elem) { if(debug) { ber_ind_save = ber_ind; ber_ind = ber_ind + " "; sys->print("%sber_decode, byte %d\n", ber_ind, i); } err, constr, length: int; tag : Tag; val : ref Value; elem : ref Elem = nil; (err, i, tag, constr) = tag_decode(a, i, n); if(err == OK) { (err, i, length) = length_decode(a, i, n); if(err == OK) { if(debug) sys->print("%sgot tag %s, length %d, now at byte %d\n", ber_ind, tag.tostring(), length, i); if(tag.class == Universal) (err, i, val) = value_decode(a, i, n, length, tag.num, constr); else (err, i, val) = value_decode(a, i, n, length, OCTET_STRING, 0); if(val != nil) elem = ref Elem(tag, val); } } if(debug) { sys->print("%send ber_decode, byte %d\n", ber_ind, i); if(val != nil) { sys->print("%sdecode result:\n", ber_ind); print_elem(elem); } if(err != OK) sys->print("%serror: %s\n", ber_ind, errstr(err, i, i)); ber_ind = ber_ind_save; } return (err, i, elem); } # Decode a tag field. As well as Tag, return an int that # is 1 if the type is constructed, 0 if not. tag_decode(a: array of byte, i, n: int) : (int, int, Tag, int) { err := OK; class, num, constr: int; if(n-i >= 2) { v := int a[i++]; class = v & CLASS_MASK; if(v & CONSTR_MASK) constr = 1; else constr = 0; num = v & TAG_MASK; if(num == TAG_MASK) # long tag number (err, i, num) = uint7_decode(a, i, n); } else err = ESHORT; return (err, i, Tag(class, num), constr); } # Decode a length field. Assume it fits in a Limbo int. # If "indefinite length", return -1. length_decode(a: array of byte, i, n: int) : (int, int, int) { err := OK; num := 0; if(i < n) { v := int a[i++]; if(v & 16r80) return int_decode(a, i, n, v&16r7F, 1); else if(v == 16r80) num = -1; else num = v; } else err = ESHORT; return (err, i, num); } # Decode a value according to the encoding of the Universal # type with number "kind" and constructed/primitive according # to "constr", with given length (may be -1, for "indefinite"). value_decode(a: array of byte, i, n, length, kind, constr: int) : (int, int, ref Value) { err := OK; val : ref Value; va : array of byte; if(length == -1) { if(!constr) err = EINVAL; } else if(i+length > n) err = EVALLEN; if(err != OK) return (err, i, nil); case kind { 0 => # marker for end of indefinite constructions if(length == 0) val = ref Value.EOC; else err = EINVAL; BOOLEAN => if(constr) err = ECONSTR; else if(length != 1) err = EVALLEN; else { val = ref Value.Bool(int a[0]); i++; } INTEGER or ENUMERATED => if(constr) err = ECONSTR; else if(length <= 4) { num : int; (err, i, num) = int_decode(a, i, i+length, length, 0); if(err == OK) val = ref Value.Int(num); } else { va = array[length] of byte; va[0:] = a[i:i+length]; val = ref Value.BigInt(va); i += length; } BIT_STRING => if(constr) { if(length == -1 && i+2 <= n && a[i] == byte 0 && a[i+1] == byte 0) { val = ref Value.BitString(0, nil); i += 2; } else # TODO: recurse and concat results err = EUNIMPL; } else { if(length < 2) { if(length == 1 && a[0] == byte 0) { val = ref Value.BitString(0, nil); i ++; } else err = EINVAL; } else { bitsunused := int a[i]; if(bitsunused > 7) err = EINVAL; else if(length > 16r0FFFFFFF) err = ETOOBIG; else { va = array[length-1] of byte; va[0:] = a[i+1:i+length]; val = ref Value.BitString(bitsunused, va); i += length; } } } OCTET_STRING or ObjectDescriptor => (err, i, va) = octet_decode(a, i, n, length, constr); if(err == OK) val = ref Value.Octets(va); NULL => if(constr) err = ECONSTR; else if(length != 0) err = EVALLEN; else val = ref Value.Null; OBJECT_ID => if(constr) err = ECONSTR; else if (length == 0) err = EVALLEN; else { subids : list of int = nil; iend := i+length; while(i < iend) { x : int; (err, i, x) = uint7_decode(a, i, n); if(err != OK) break; subids = x :: subids; } if(err == OK) { if(i != iend) err = EVALLEN; else { m := len subids; ia := array[m+1] of int; while(subids != nil) { y := hd subids; subids = tl subids; if(m == 1) { ia[1] = y % 40; ia[0] = y / 40; } else ia[m--] = y; } val = ref Value.ObjId(ref Oid(ia)); } } } EXTERNAL or EMBEDDED_PDV => # TODO: parse this internally va = array[length] of byte; va[0:] = a[i:i+length]; val = ref Value.Other(va); i += length; REAL => # let the appl decode, with math module if(constr) err = ECONSTR; else { va = array[length] of byte; va[0:] = a[i:i+length]; val = ref Value.Real(va); i += length; } SEQUENCE or SET=> vl : list of ref Elem; (err, i, vl) = seq_decode(a, i, n, length, constr); if(err == OK) { if(kind == SEQUENCE) val = ref Value.Seq(vl); else val = ref Value.Set(vl); } NumericString or PrintableString or TeletexString or VideotexString or IA5String or UTCTime or GeneralizedTime or GraphicString or VisibleString or GeneralString or UniversalString or BMPString => (err, i, va) = octet_decode(a, i, n, length, constr); if(err == OK) # sometimes wrong: need to do char set conversion val = ref Value.String(string va); * => va = array[length] of byte; va[0:] = a[i:i+length]; val = ref Value.Other(va); i += length; } return (err, i, val); } # Decode an int in format where count bytes are # concatenated to form value. # Although ASN1 allows any size integer, we return # an error if the result doesn't fit in a Limbo int. # If unsigned is not set, make sure to propagate sign bit. int_decode(a: array of byte, i, n, count, unsigned: int) : (int, int, int) { err := OK; num := 0; if(n-i >= count) { if((count > 4) || (unsigned && count == 4 && (int a[i] & 16r80))) err = ETOOBIG; else { if(!unsigned && count > 0 && count < 4 && (int a[i] & 16r80)) num = -1; # all bits set for(j := 0; j < count; j++) { v := int a[i++]; num = (num << 8) | v; } } } else err = ESHORT; return (err, i, num); } # Decode an unsigned int in format where each # byte except last has high bit set, and remaining # seven bits of each byte are concatenated to form value. # Although ASN1 allows any size integer, we return # an error if the result doesn't fit in a Limbo int. uint7_decode(a: array of byte, i, n: int) : (int, int, int) { err := OK; num := 0; more := 1; while(more && i < n) { v := int a[i++]; if(num & 16r7F000000) { err = ETOOBIG; break; } num <<= 7; more = v & 16r80; num |= (v & 16r7F); } if(n == i) err = ESHORT; return (err, i, num); } # Decode an octet string, recursively if constr. # We've already checked that length==-1 implies constr==1, # and otherwise that specified length fits within a[i..n]. octet_decode(a: array of byte, i, n, length, constr: int) : (int, int, array of byte) { err := OK; va : array of byte; if(length >= 0 && !constr) { va = array[length] of byte; va[0:] = a[i:i+length]; i += length; } else { # constructed, either definite or indefinite length lva : list of array of byte = nil; elem : ref Elem; istart := i; totbytes := 0; cloop: for(;;) { if(length >= 0 && i >= istart+length) { if(i != istart+length) err = EVALLEN; break cloop; } oldi := i; (err, i, elem) = ber_decode(a, i, n); if(err != OK) break; pick v := elem.val { Octets => lva = v.bytes :: lva; totbytes += len v.bytes; EOC => if(length != -1) { i = oldi; err = EINVAL; } break cloop; * => i = oldi; err = EINVAL; break cloop; } } if(err == OK) { va = array[totbytes] of byte; j := totbytes; while(lva != nil) { x := hd lva; lva = tl lva; m := len x; va[j-m:] = x[0:]; j -= m; } } } return (err, i, va); } # Decode a sequence or set. # We've already checked that length==-1 implies constr==1, # and otherwise that specified length fits within a[i..n]. seq_decode(a : array of byte, i, n, length, constr: int) : (int, int, list of ref Elem) { err := OK; ans : list of ref Elem = nil; if(!constr) err = EPRIM; else { # constructed, either definite or indefinite length lve : list of ref Elem = nil; elem : ref Elem; istart := i; cloop: for(;;) { if(length >= 0 && i >= istart+length) { if(i != istart+length) err = EVALLEN; break cloop; } oldi := i; (err, i, elem) = ber_decode(a, i, n); if(err != OK) break; pick v := elem.val { EOC => if(length != -1) { i = oldi; err = EINVAL; } break cloop; * => lve = elem :: lve; } } if(err == OK) { # reverse back to original order while(lve != nil) { e := hd lve; lve = tl lve; ans = e :: ans; } } } return (err, i, ans); } # Encode e by BER rules encode(e: ref Elem) : (string, array of byte) { (err, n) := enc(nil, e, 0, 1); if(err != "") return (err, nil); b := array[n] of byte; enc(b, e, 0, 0); return ("", b); } # Encode e into array b, only putting in bytes if !lenonly. # Start at loc i, return index after. enc(b: array of byte, e: ref Elem, i, lenonly: int) : (string, int) { (err, vlen, constr) := val_enc(b, e, 0, 1); if(err != "") return (err, i); tag := e.tag; v := tag.class | constr; if(tag.num < 31) { if(!lenonly) b[i] = byte (v | tag.num); i++; } else { if(!lenonly) b[i] = byte (v | 31); if(tag.num < 0) return ("negative tag number", i); i = uint7_enc(b, tag.num, i+1, lenonly); } if(vlen < 16r80) { if(!lenonly) b[i] = byte vlen; i++; } else { ilen := int_enc(b, vlen, 1, 0, 1); if(!lenonly) { b[i] = byte (16r80 | ilen); i = int_enc(b, vlen, 1, i+1, 0); } else i += 1+ilen; } if(!lenonly) val_enc(b, e, i, 0); i += vlen; return ("", i); } # Encode e.val into array b, only putting in bytes if !lenonly. # Start at loc i, return (err, index after, constructed or primitive) val_enc(b: array of byte, e: ref Elem, i, lenonly: int) : (string, int, int) { kind := e.tag.num; cl := e.tag.class; ok := 1; v : int; bb : array of byte; constr := 0; if(cl != Universal) { pick vv := e.val { Bool => kind = BOOLEAN; Int => kind = INTEGER; BigInt => kind = INTEGER; Octets => kind = OCTET_STRING; Real => kind = REAL; Other => kind = OCTET_STRING; BitString => kind = BIT_STRING; Null => kind = NULL; ObjId => kind = OBJECT_ID; String => kind = UniversalString; Seq => kind = SEQUENCE; Set => kind = SET; } } case kind { BOOLEAN => (ok, v) = e.is_int(); if(ok) { if(v != 0) v = 255; i = int_enc(b, v, 1, i, lenonly); } INTEGER or ENUMERATED => (ok, v) = e.is_int(); if(ok) i = int_enc(b, v, 0, i, lenonly); else { (ok, bb) = e.is_bigint(); if(ok) { if(!lenonly) b[i:] = bb; i += len bb; } } BIT_STRING => (ok, v, bb) = e.is_bitstring(); if(ok) { if(bb == nil) { if(!lenonly) b[i] = byte 0; i++; } else { if(v < 0 || v > 7) ok = 0; else { if(!lenonly) { b[i] = byte v; b[i+1:] = bb; } i += 1 + len bb; } } } OCTET_STRING or ObjectDescriptor or EXTERNAL or REAL or EMBEDDED_PDV => pick vv := e.val { Octets or Real or Other => if(!lenonly && vv.bytes != nil) b[i:] = vv.bytes; i += len vv.bytes; * => ok = 0; } NULL => ; OBJECT_ID => oid : ref Oid; (ok, oid) = e.is_oid(); if(ok) { n := len oid.nums; for(k := 0; k < n; k++) { v = oid.nums[k]; if(k == 0) { v *= 40; if(n > 1) v += oid.nums[++k]; } i = uint7_enc(b, v, i, lenonly); } } SEQUENCE or SET => pick vv := e.val { Seq or Set => constr = CONSTR_MASK; for(l := vv.l; l != nil; l = tl l) { err : string; (err, i) = enc(b, hd l, i, lenonly); if(err != "") return (err, i, 0); } } NumericString or PrintableString or TeletexString or VideotexString or IA5String or UTCTime or GeneralizedTime or GraphicString or VisibleString or GeneralString or UniversalString or BMPString => pick vv := e.val { String => bb = array of byte vv.s; if(!lenonly && bb != nil) b[i:] = bb; i += len bb; * => ok = 0; } * => ok = 0; } if(!ok) return ("bad value for encoding kind", i, constr); return ("", i, constr); } # Encode num as unsigned 7 bit values with top bit 1 on all bytes # except last, into array b, only putting in bytes if !lenonly. # Start at loc i, return index after. uint7_enc(b: array of byte, num, i, lenonly: int) : int { n := 1; v := num>>7; while(v > 0) { v >>= 7; n++; } if(lenonly) i += n; else { for(k := (n-1)*7; k > 0; k -= 7) b[i++] = byte ((num>>k) | 16r80); b[i++] = byte (num & 16r7F); } return i; } # Encode num as unsigned or signed integer into array b, # only putting in bytes if !lenonly. # Encoding is length followed by bytes to concatenate. # Start at loc i, return index after. int_enc(b: array of byte, num, unsigned, i, lenonly: int) : int { v := num; if(v < 0) v = -(v+1); n := 1; prevv := v; v >>= 8; while(v > 0) { prevv = v; v >>= 8; n++; } if(!unsigned && (prevv & 16r80)) n++; if(lenonly) i += n; else { for(k := (n-1)*8; k >= 0; k -= 8) b[i++] = byte (num>>k); } return i; } # Compare two arrays of integers; return true if they match intarr_eq(a: array of int, b: array of int) : int { alen := len a; if(alen != len b) return 0; for(i := 0; i < alen; i++) if(a[i] != b[i]) return 0; return 1; } # Look for o in tab; if found, return index, else return -1. oid_lookup(o: ref Oid, tab: array of Oid) : int { for(i := 0; i < len tab; i++) if(intarr_eq(o.nums, tab[i].nums)) return i; return -1; } # If e is a SEQUENCE, return (1, e's element list) # else return (error, nil). Elem.is_seq(e: self ref Elem) : (int, list of ref Elem) { if(e.tag.class == Universal && e.tag.num == SEQUENCE) { pick v := e.val { Seq => return (1, v.l); } } return (0, nil); } # If e is a SET, return (1, e's element list) # else return (error, nil). Elem.is_set(e: self ref Elem) : (int, list of ref Elem) { if(e.tag.class == Universal && e.tag.num == SET) { pick v := e.val { Set => return (1, v.l); } } return (0, nil); } # If e is an INTEGER that fits in a limbo int, return (1, val) # else return (0, 0l). Elem.is_int(e: self ref Elem) : (int, int) { if(e.tag.class == Universal && (e.tag.num == INTEGER || e.tag.num == BOOLEAN)) { pick v := e.val { Int => return (1, v.v); } } return (0, 0); } # If e is an INTEGER that doesn't fit in a limbo int, return (1, bytes), # or even if it does fit, return it as an array of bytes. # else return (0, nil). Elem.is_bigint(e: self ref Elem) : (int, array of byte) { if(e.tag.class == Universal && e.tag.num == INTEGER) { pick v := e.val { BigInt => return (1, v.bytes); Int => x := v.v; a := array[4] of byte; for(i := 0; i < 4; i++) a[i] = byte ((x >> (8*(3-i))) & 16rFF); for(j := 0; j < 3; j++) if(a[j] != byte 0) break; return (1, a[j:]); } } return (0, nil); } # If e is a bitstring, return (1, unused bits, bytes containing bit string), # else return (0, nil) Elem.is_bitstring(e: self ref Elem) : (int, int, array of byte) { if(e.tag.class == Universal && e.tag.num == BIT_STRING) { pick v := e.val { BitString => return (1, v.unusedbits, v.bits); } } return (0, 0, nil); } # If e is an octetstring, return (1, bytes), # else return (0, nil) Elem.is_octetstring(e: self ref Elem) : (int, array of byte) { if(e.tag.class == Universal && e.tag.num == OCTET_STRING) { pick v := e.val { Octets => return (1, v.bytes); } } return (0, nil); } # If e is an object id, return (1, ref Oid), # else return (0, nil) Elem.is_oid(e: self ref Elem) : (int, ref Oid) { if(e.tag.class == Universal && e.tag.num == OBJECT_ID) { pick v := e.val { ObjId => return (1, v.id); } } return (0, nil); } # If e is some kind of string (excluding times), return (1, string), # else return (0, "") Elem.is_string(e: self ref Elem) : (int, string) { if(e.tag.class == Universal) { case e.tag.num { NumericString or PrintableString or TeletexString or VideotexString or IA5String or GraphicString or VisibleString or GeneralString or UniversalString or BMPString => pick v := e.val { String => return (1, v.s); } } } return (0, nil); } # If e is some kind of time, return (1, string), # else return (0, "") Elem.is_time(e: self ref Elem) : (int, string) { if(e.tag.class == Universal && (e.tag.num == UTCTime || e.tag.num == GeneralizedTime)) { pick v := e.val { String => return (1, v.s); } } return (0, nil); } # Return printable error string for code ecode. # i is position where error is first noted. # n is the end of the passed data: if i!=n then # we didn't use all the data and an error should # be returned about that. errstr(ecode, i, n: int) : string { if(ecode == OK && i == n) return ""; err := "BER decode: "; case ecode { OK => err += "OK"; ESHORT => err += "need more data"; ETOOBIG => err += "value exceeds implementation limit"; EVALLEN => err += "value has wrong length"; ECONSTR => err += "value is constructed, should be primitive"; EPRIM => err += "value is primitive"; EINVAL => err += "value encoding invalid"; * => err += "unknown error " + string ecode; } if(err == "" && i != n) err += "extra data"; err += " at byte " + string i; return err; } # Printing functions, for debugging Tag.tostring(t: self Tag) : string { ans := ""; snum := string t.num; if(t.class == Universal) { case t.num { BOOLEAN => ans = "BOOLEAN"; INTEGER => ans = "INTEGER"; BIT_STRING => ans = "BIT STRING"; OCTET_STRING => ans = "OCTET STRING"; NULL => ans = "NULL"; OBJECT_ID => ans = "OBJECT IDENTIFER"; ObjectDescriptor => ans = "OBJECT_DES"; EXTERNAL => ans = "EXTERNAL"; REAL => ans = "REAL"; ENUMERATED => ans = "ENUMERATED"; EMBEDDED_PDV => ans = "EMBEDDED PDV"; SEQUENCE => ans = "SEQUENCE"; SET => ans = "SET"; NumericString => ans = "NumericString"; PrintableString => ans = "PrintableString"; TeletexString => ans = "TeletexString"; VideotexString => ans = "VideotexString"; IA5String => ans = "IA5String"; UTCTime => ans = "UTCTime"; GeneralizedTime => ans = "GeneralizedTime"; GraphicString => ans = "GraphicString"; VisibleString => ans = "VisibleString"; GeneralString => ans = "GeneralString"; UniversalString => ans = "UniversalString"; BMPString => ans = "BMPString"; * => ans = "UNIVERSAL " + snum; } } else { case t.class { Application => ans = "APPLICATION " + snum; Context => ans = "CONTEXT "+ snum; Private => ans = "PRIVATE " + snum; } } return ans; } Elem.tostring(e: self ref Elem) : string { return estring(e, ""); } Value.tostring(v: self ref Value) : string { return vstring(v, ""); } estring(e: ref Elem, indent: string) : string { return indent + e.tag.tostring() + " " + vstring(e.val, indent); } vstring(val: ref Value, indent: string) : string { ans := ""; pick v := val { Bool or Int => ans += string v.v; Octets or BigInt or Real or Other => ans += bastring(v.bytes, indent + "\t"); BitString => ans += " bits (unused " +string v.unusedbits + ")" + bastring(v.bits, indent + "\t"); Null or EOC => ; ObjId => ans += v.id.tostring(); String => ans += "\"" + v.s + "\""; Seq or Set => ans += "{\n"; newindent := indent + "\t"; l := v.l; while(l != nil) { if(ans[len ans-1] != '\n') ans[len ans] = '\n'; ans += estring(hd l, newindent); l = tl l; } if(ans[len ans-1] != '\n') ans[len ans] = '\n'; ans += indent + "}"; } return ans; } bastring(a: array of byte, indent: string) : string { if(sys == nil) sys = load Sys Sys->PATH; ans := indent; nlindent := "\n" + indent; for(i := 0; i < len a; i++) { if(i < len a - 1 && i%10 == 0) ans += nlindent ; ans += sys->sprint("%2x ", int a[i]); } return ans; } Oid.tostring(o: self ref Oid) : string { ans := ""; for(i := 0; i < len o.nums; i++) { ans += string o.nums[i]; if(i < len o.nums - 1) ans[len ans] = '.'; } return ans; } print_elem(e: ref Elem) { s := e.tostring(); a := array of byte s; sys->write(sys->fildes(1), a, len a); sys->print("\n"); }