tlv 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ module TLV
2
+ class DGI < TLV
3
+
4
+ def self.get_length(bytes)
5
+ len = bytes[0] #CPS 2.2 Creation of ...
6
+ if len == 0xFF
7
+ len = bytes[1,2].unpack("n")[0]
8
+ end
9
+ num = len > 0xfe ? 3 : 1
10
+ rest = bytes [num, bytes.length]
11
+ [len, rest]
12
+ end
13
+
14
+ def self.check_tag
15
+ raise "incorrect tag length, dgi must be 2 : #{tag}" unless tag.length==2
16
+ end
17
+
18
+ def self.get_tag bytes
19
+ tag = bytes[0,2]
20
+ rest = bytes [2,bytes.length]
21
+ [tag, rest]
22
+ end
23
+
24
+ def get_len_bytes len
25
+ bytes = case len
26
+ when 0..0xfe : "" << len
27
+ when 0xff..65534 : "\xff"+[len].pack("n")
28
+ else
29
+ raise "Don't be silly"
30
+ end
31
+ end
32
+
33
+ class << self
34
+
35
+ def primitive?
36
+ false
37
+ end
38
+ end
39
+ end
40
+ end # module
@@ -0,0 +1,24 @@
1
+ module TLV
2
+ class TLV
3
+ class Field
4
+ attr_accessor :display_name, :name, :length
5
+ def initialize clazz, desc, name=nil, len=0
6
+ @length=len
7
+ unless name
8
+ name = desc.gsub(/\s/, "_")
9
+ name = name.gsub(/\W/, "")
10
+ name = name.downcase.to_sym
11
+ end
12
+ @name = name
13
+ @display_name = desc
14
+ define_accessor clazz
15
+ end
16
+ def parse tlv, bytes, length
17
+ raise "not implemented! use subclass"
18
+ end
19
+ def define_accessor clazz
20
+ #raise "Not primitive: #{clazz.to_s}, TAG=#{TLV.b2s clazz.tag}" unless clazz.primitive?
21
+ end
22
+ end
23
+ end
24
+ end # module
@@ -0,0 +1,87 @@
1
+ module TLV
2
+ class TLV
3
+ # return [tlv, rest], the parsed TLV and any leftover bytes.
4
+ def self.parse bytes
5
+ tlv, _ = self._parse bytes
6
+ return tlv
7
+ end
8
+
9
+ def self._parse bytes
10
+ return nil unless bytes && bytes.length>0
11
+ tag, _ = self.get_tag bytes
12
+ impl = lookup(tag)
13
+ tlv = impl.new
14
+ rest = tlv.parse(bytes)
15
+ [tlv, rest]
16
+ end
17
+
18
+ def self.get_tag bytes
19
+ tag = (bytes[0,1])
20
+ if (tag[0] & 0x1f) == 0x1f # last 5 bits set, 2 byte tag
21
+ tag = bytes[0,2]
22
+ if (tag[1] & 0x80) == 0x80 # bit 8 set -> 3 byte tag
23
+ tag = bytes[0,3]
24
+ end
25
+ end
26
+ [tag, bytes[tag.length, bytes.length]]
27
+ end
28
+
29
+ def self.get_length bytes
30
+ len = bytes[0,1][0]
31
+ num_bytes=0
32
+
33
+ if (len & 0x80) == 0x80 # if MSB set
34
+ num_bytes = len & 0x0F # 4 LSB are num bytes total
35
+ raise "Don't be silly: #{b2s(bytes)}" if num_bytes > 4
36
+ len = bytes[1,num_bytes]
37
+ len = ("#{"\x00"*(4-num_bytes)}%s" % len).unpack("N")[0]
38
+ end
39
+ # this will return ALL the rest, not just `len` bytes of the rest. Does this make sense?
40
+ rest = bytes[1+num_bytes, bytes.length]
41
+ # TODO handle errors...
42
+ # warn if rest.length > length || rest.length < length ?
43
+ [len, rest]
44
+ end
45
+
46
+ def initialize bytes=nil
47
+ parse(bytes) if bytes
48
+ end
49
+
50
+ # returns the leftover bytes
51
+ def parse bytes
52
+ if tag
53
+ tag, rest = TLV.get_tag(bytes)
54
+ length, bytes = self.class.get_length(rest)
55
+ end
56
+
57
+ if self.class.primitive?
58
+ bytes = parse_fields bytes, length
59
+ else
60
+ bytes = parse_tlv bytes, length
61
+ end
62
+
63
+ bytes
64
+
65
+ end
66
+
67
+ def parse_tlv bytes, length
68
+ b = bytes[0,length]
69
+ rest = bytes[length, bytes.length]
70
+
71
+ while b && b.length != 0
72
+ tlv, b = self.class._parse(b)
73
+ self.send("#{tlv.class.accessor_name}=", tlv)
74
+ end
75
+
76
+ rest
77
+ end
78
+ def parse_fields bytes, length
79
+ fields.each { |field|
80
+ bytes = field.parse(self, bytes, length)
81
+ } if fields
82
+ bytes
83
+ end
84
+
85
+
86
+ end
87
+ end # module
@@ -0,0 +1,63 @@
1
+ #universal numbers, source http://en.wikipedia.org/wiki/Basic_Encoding_Rules
2
+ #
3
+ TLV::UNIVERSAL = [
4
+ ['EOC (End-of-Content)',"P",0x00],
5
+ ['BOOLEAN',"P",0x01],
6
+ ['INTEGER',"P",0x02],
7
+ ['BIT STRING',"P/C",0x03],
8
+ ['OCTET STRING',"P/C",0x04],
9
+ ['NULL',"P",0x05],
10
+ ['OBJECT IDENTIFIER',"P",0x06],
11
+ ['Object Descriptor',"P",0x07],
12
+ ['EXTERNAL',"C",0x08],
13
+ ['REAL (float)',"P",0x09],
14
+ ['ENUMERATED',"P",0x0A],
15
+ ['EMBEDDED PDV',"C",0x0B],
16
+ ['UTF8String',"P/C",0x0C],
17
+ ['RELATIVE-OID',"P",0x0D],
18
+ ['SEQUENCE and SEQUENCE OF',"C",0x10],
19
+ ['SET and SET OF',"C",0x11],
20
+ ['NumericString',"P/C",0x12],
21
+ ['PrintableString',"P/C",0x13],
22
+ ['T61String',"P/C",0x14],
23
+ ['VideotexString',"P/C",0x15],
24
+ ['IA5String',"P/C",0x16],
25
+ ['UTCTime',"P/C",0x17],
26
+ ['GeneralizedTime',"P/C",0x18],
27
+ ['GraphicString',"P/C",0x19],
28
+ ['VisibleString',"P/C",0x1A],
29
+ ['GeneralString',"P/C",0x1B],
30
+ ['UniversalString',"P/C",0x1C],
31
+ ['CHARACTER STRING',"P/C",0x1D],
32
+ ['BMPString',"P/C",0x1E],
33
+ ]
34
+
35
+ def make_dict
36
+ def add_primitive e,d
37
+ tag = ("" << e[2])
38
+ d[tag] = e[0]
39
+ end
40
+ def add_const e,d
41
+ tag = "" << (e[2] | 0x20)
42
+ d[tag] = e[0]
43
+ end
44
+ dict = {}
45
+ TLV::UNIVERSAL.each {|entry|
46
+ case entry[1]
47
+ when "P"
48
+ add_primitive entry, dict
49
+ when "C"
50
+ add_const entry, dict
51
+ when "P/C"
52
+ add_primitive entry, dict
53
+ add_const entry, dict
54
+ end
55
+ }
56
+ TLV::DICTIONARIES["ASN"] = dict
57
+ end
58
+ make_dict
59
+
60
+ #TLV::DICTIONARIES["ASN"].each_pair{|key,value|
61
+ # puts "#{TLV.b2s(key)} #{value}"
62
+ #}
63
+
@@ -0,0 +1,2 @@
1
+ require 'tlv/parser/dictionaries/emv_tags'
2
+ require 'tlv/parser/dictionaries/asn'
@@ -0,0 +1,130 @@
1
+ TLV::DICTIONARIES["EMV"] = {
2
+ "\x42" => "Issuer Identification Number (IIN)",
3
+ "\x4F" => "Application Identifier (AID) - card",
4
+ "\x4F" => "AID",
5
+ "\x50" => "Application Label",
6
+ "\x57" => "Track 2 Equivalent Data",
7
+ "\x5A" => "Application Primary Account Number (PAN)",
8
+ "\x5A" => "PAN",
9
+ "\x5F\x20" => "Cardholder Name",
10
+ "\x5F\x24" => "Application Expiration Date",
11
+ "\x5F\x25" => "Application Effective Date",
12
+ "\x5F\x28" => "Issuer Country Code",
13
+ "\x5F\x2A" => "Transaction Currency Code",
14
+ "\x5F\x2D" => "Language Preference",
15
+ "\x5F\x30" => "Service Code",
16
+ "\x5F\x34" => "Application Primary Account Number (PAN) Sequence Number",
17
+ "\x5F\x36" => "Transaction Currency Exponent",
18
+ "\x5F\x50" => "Issuer URL",
19
+ "\x5F\x53" => "International Bank Account Number (IBAN)",
20
+ "\x5F\x54" => "Bank Identifier Code (BIC)",
21
+ "\x5F\x55" => "Issuer Country Code (alpha2 format)",
22
+ "\x5F\x56" => "Issuer Country Code (alpha3 format)",
23
+ "\x61" => "Application Template",
24
+ "\x6F" => "File Control Information (FCI) Template",
25
+ "\x70" => "READ RECORD Response Message Template",
26
+ "\x71" => "Issuer Script Template 1",
27
+ "\x72" => "Issuer Script Template 2",
28
+ "\x73" => "Directory Discretionary Template",
29
+ "\x77" => "Response Message Template Format 2",
30
+ "\x80" => "Response Message Template Format 1",
31
+ "\x81" => "Amount, Authorised (Binary)",
32
+ "\x82" => "Application Interchange Profile",
33
+ "\x83" => "Command Template",
34
+ "\x84" => "Dedicated File (DF) Name",
35
+ "\x86" => "Issuer Script Command",
36
+ "\x87" => "Application Priority Indicator",
37
+ "\x88" => "Short File Identifier (SFI)",
38
+ "\x88" => "SFI",
39
+ "\x89" => "Authorisation Code",
40
+ "\x8A" => "Authorisation Response Code",
41
+ "\x8C" => "Card Risk Management Data Object List 1 (CDOL1)",
42
+ "\x8C" => "CDOL1",
43
+ "\x8D" => "Card Risk Management Data Object List 2 (CDOL2)",
44
+ "\x8D" => "CDOL2",
45
+ "\x8E" => "Cardholder Verification Method (CVM) List",
46
+ "\x8E" => "CVM List",
47
+ "\x8F" => "Certification Authority Public Key Index",
48
+ "\x90" => "Issuer Public Key Certificate",
49
+ "\x91" => "Issuer Authentication Data",
50
+ "\x92" => "Issuer Public Key Remainder",
51
+ "\x93" => "Signed Static Application Data",
52
+ "\x94" => "Application File Locator (AFL)",
53
+ "\x94" => "Application File Locator",
54
+ "\x95" => "Terminal Verification Results",
55
+ "\x97" => "Transaction Certificate Data Object List (TDOL)",
56
+ "\x98" => "Transaction Certificate (TC) Hash Value",
57
+ "\x99" => "Transaction Personal Identification Number (PIN) Data",
58
+ "\x9A" => "Transaction Date",
59
+ "\x9B" => "Transaction Status Information",
60
+ "\x9C" => "Transaction Type",
61
+ "\x9D" => "Directory Definition File (DDF) Name",
62
+ "\x9F\x01" => "Acquirer Identifier",
63
+ "\x9F\x02" => "Amount, Authorised (Numeric)",
64
+ "\x9F\x03" => "Amount, Other (Numeric)",
65
+ "\x9F\x04" => "Amount, Other (Binary)",
66
+ "\x9F\x05" => "Application Discretionary Data",
67
+ "\x9F\x06" => "Application Identifier (AID) - terminal",
68
+ "\x9F\x07" => "Application Usage Control",
69
+ "\x9F\x08" => "Application Version Number",
70
+ "\x9F\x09" => "Application Version Number",
71
+ "\x9F\x0B" => "Cardholder Name Extended",
72
+ "\x9F\x0D" => "Issuer Action Code - Default",
73
+ "\x9F\x0E" => "Issuer Action Code - Denial",
74
+ "\x9F\x0F" => "Issuer Action Code - Online",
75
+ "\x9F\x10" => "Issuer Application Data",
76
+ "\x9F\x11" => "Issuer Code Table Index",
77
+ "\x9F\x12" => "Application Preferred Name",
78
+ "\x9F\x13" => "Last Online Application Transaction Counter (ATC) Register",
79
+ "\x9F\x14" => "Lower Consecutive Offline Limit",
80
+ "\x9F\x15" => "Merchant Category Code",
81
+ "\x9F\x16" => "Merchant Identifier",
82
+ "\x9F\x17" => "Personal Identification Number (PIN) Try Counter",
83
+ "\x9F\x18" => "Issuer Script Identifier",
84
+ "\x9F\x1A" => "Terminal Country Code",
85
+ "\x9F\x1B" => "Terminal Floor Limit",
86
+ "\x9F\x1C" => "Terminal Identification",
87
+ "\x9F\x1D" => "Terminal Risk Management Data",
88
+ "\x9F\x1E" => "Interface Device (IFD) Serial Number",
89
+ "\x9F\x1F" => "Track 1 Discretionary Data",
90
+ "\x9F\x20" => "Track 2 Discretionary Data",
91
+ "\x9F\x21" => "Transaction Time",
92
+ "\x9F\x23" => "Upper Consecutive Offline Limit",
93
+ "\x9F\x26" => "Application Cryptogram",
94
+ "\x9F\x27" => "Cryptogram Information Data",
95
+ "\x9F\x2D" => "ICC PIN Encipherment Public Key Certificate",
96
+ "\x9F\x2E" => "ICC PIN Encipherment Public Key Exponent",
97
+ "\x9F\x2F" => "ICC PIN Encipherment Public Key Remainder",
98
+ "\x9F\x32" => "Issuer Public Key Exponent",
99
+ "\x9F\x33" => "Terminal Capabilities",
100
+ "\x9F\x34" => "Cardholder Verification Method (CVM) Results",
101
+ "\x9F\x35" => "Terminal Type",
102
+ "\x9F\x36" => "Application Transaction Counter (ATC)",
103
+ "\x9F\x37" => "Unpredictable Number",
104
+ "\x9F\x38" => "Processing Options Data Object List (PDOL)",
105
+ "\x9F\x38" => "PDOL",
106
+ "\x9F\x39" => "Point-of-Service (POS) Entry Mode",
107
+ "\x9F\x3A" => "Amount, Reference Currency",
108
+ "\x9F\x3B" => "Application Reference Currency",
109
+ "\x9F\x3C" => "Transaction Reference Currency Code",
110
+ "\x9F\x3D" => "Transaction Reference Currency Exponent",
111
+ "\x9F\x40" => "Additional Terminal Capabilities",
112
+ "\x9F\x41" => "Transaction Sequence Counter",
113
+ "\x9F\x42" => "Application Currency Code",
114
+ "\x9F\x43" => "Application Reference Currency Exponent",
115
+ "\x9F\x44" => "Application Currency Exponent",
116
+ "\x9F\x45" => "Data Authentication Code",
117
+ "\x9F\x46" => "ICC Public Key Certificate",
118
+ "\x9F\x47" => "ICC Public Key Exponent",
119
+ "\x9F\x48" => "ICC Public Key Remainder",
120
+ "\x9F\x49" => "Dynamic Data Authentication Data Object List (DDOL)",
121
+ "\x9F\x49" => "DDOL",
122
+ "\x9F\x4A" => "Static Data Authentication Tag List",
123
+ "\x9F\x4B" => "Signed Dynamic Application Data",
124
+ "\x9F\x4C" => "ICC Dynamic Number",
125
+ "\x9F\x4D" => "Log Entry",
126
+ "\x9F\x4E" => "Merchant Name and Location",
127
+ "\x9F\x4F" => "Log Format",
128
+ "\xA5" => "File Control Information (FCI) Proprietary Template",
129
+ "\xBF\x0C" => "File Control Information (FCI) Issuer Discretionary Data",
130
+ }
@@ -0,0 +1,167 @@
1
+ # Simple lib to deconstruct tlv data
2
+ # TLV.parse str
3
+ # TLV.parse_hex str
4
+ require 'hexy'
5
+
6
+ module TLV
7
+ DICTIONARIES = {}
8
+ # Attempt to parse a (series) or BER encoded
9
+ # datastructures. May be be passed a
10
+ #
11
+ # "\x00"=> "Tag Name"
12
+ #
13
+ # encoded dictionary to provide names for tags.
14
+ # Some dictionaries are predefined in TLV::DICTIONARIES
15
+ #
16
+ # Parameters:
17
+ # +bytes+ : a string of raw bytes to decode
18
+ # +dictionary+ : a tag=>name dictionary for tagname lookup
19
+ #
20
+ # Returns:
21
+ # a string respresenation of the datastructure
22
+ def self.parse bytes, dictionary={}
23
+ _dump(_parse(bytes), dictionary)
24
+ end
25
+
26
+ #
27
+ # Attempt to decode a (series) of BER encoded
28
+ # datastructures (see parse)
29
+ #
30
+ # The data passed to this method is expected to
31
+ # be hex formated instead of in binary form.
32
+ #
33
+ def self.parse_hex hex_str, dictionary={}
34
+ self.parse s2b(hex_str), dictionary
35
+ end
36
+
37
+ #
38
+ # Attempt to decode a DGI encoded datastructure.
39
+ # This is used in EMV (CPS).
40
+ # see parse
41
+ #
42
+ def self.parse_dgi bytes, dictionary={}
43
+ _dump(_parse_dgi(bytes), dictionary)
44
+ end
45
+
46
+ #
47
+ # Attempt to decode a DGI encoded datastructure.
48
+ # This is used in EMV (CPS).
49
+ # see parse_hex
50
+ #
51
+ def self.parse_dgi_hex hex_str, dictionary={}
52
+ self.parse_dgi s2b(hex_str), dictionary
53
+ end
54
+ # heuristics to determine whether a string of bytes
55
+ # is worth printing. If more than 90% of bytes are
56
+ # printable, will return true.
57
+ def self.printable? bytes
58
+ count = 0
59
+ count_printable = 0
60
+ bytes.each_byte {|b|
61
+ count += 1
62
+ count_printable += 1 if ((0x20..0x7e).include?(b) || (0xA0..0xff).include?(b))
63
+ }
64
+ return (count_printable.to_f/count) > 0.90
65
+ end
66
+
67
+ #--
68
+ # Stop RDOC here.
69
+ def self._parse bytes #:nodoc:
70
+ tlvs = []
71
+ begin
72
+ tlv, rest = _parse_tlv bytes
73
+ tlvs << tlv
74
+ bytes = rest
75
+ end while rest && rest.length != 0
76
+ tlvs
77
+ end
78
+
79
+ def self._parse_dgi bytes #:nodoc:
80
+ dgis = []
81
+ begin
82
+ dgi = TLV_Object.new
83
+ tag, rest = DGI.get_tag bytes
84
+ dgi.tag = tag
85
+ len, rest = DGI.get_length rest
86
+ dgi.length = len
87
+ if tag == "\x80\x00" || tag == "\x90\x00"
88
+ # no children
89
+ dgi.value = rest[0,len]
90
+ else
91
+ dgi.children = _parse rest[0,len]
92
+ end
93
+ dgis << dgi
94
+ bytes = rest[len, rest.length]
95
+ end while bytes && bytes.length != 0
96
+ dgis
97
+ end
98
+
99
+
100
+ def self._parse_tlv bytes #:nodoc:
101
+ tlv = TLV_Object.new
102
+ tag, rest = TLV.get_tag bytes
103
+ tlv.tag = tag
104
+ len, rest = TLV.get_length rest
105
+ tlv.length = len
106
+ if (tag[0] & 0x20) != 0x00 # constructed object
107
+ tlv.children = _parse rest[0,len]
108
+ else
109
+ tlv.value = rest[0,len]
110
+ end
111
+ [tlv, rest[len, rest.length]]
112
+ end
113
+
114
+ def self._dump (tlvs, dictionary, indent = "") #:nodoc:
115
+ dump = ""
116
+ tlvs.each {|tlv|
117
+ dump += "%s%-6s : %d" % [indent, "0x"+b2s(tlv.tag), tlv.length]
118
+ if tlv.children
119
+ dump += " ["+dictionary[tlv.tag]+"]" if (dictionary[tlv.tag])
120
+ dump += "\n"
121
+ tlv.children.each {|child|
122
+ dump += _dump([child], dictionary, indent+" ")
123
+ }
124
+ else
125
+ if (tlv.value.length < 17)
126
+ dump += " : " + b2s(tlv.value)
127
+ dump += " (#{tlv.value})" if printable?(tlv.value)
128
+ dump += " ["+dictionary[tlv.tag]+"]" if (dictionary[tlv.tag])
129
+ else
130
+ dump += " ["+dictionary[tlv.tag]+"]" if (dictionary[tlv.tag])
131
+ dump +="\n"
132
+ dump += Hexy.dump(tlv.value, :indent=>indent.length+2)
133
+ end
134
+
135
+ dump += "\n"
136
+ end
137
+ }
138
+ #puts ">>>>"
139
+ #puts dump
140
+ #puts "<<<<"
141
+ dump
142
+ end
143
+ class TLV_Object
144
+ attr_accessor :tag, :length, :value, :children
145
+ end
146
+ end
147
+
148
+ if $0 == __FILE__
149
+ require "tlv"
150
+ # puts TLV.parse_hex("9f7103313233")
151
+ # puts
152
+ # puts TLV.parse_hex("32084102010243023456")
153
+ # puts
154
+ # puts TLV.parse_hex("320841020102430234")
155
+ # puts
156
+ # dict = {"\x32" => "Test Tag", "\x41" => "Other Tag"}
157
+ # puts TLV.parse_hex("32084102010243023456", dict)
158
+ # puts
159
+ # puts TLV.parse_hex("320841020102430234569f7103313233", dict)
160
+ # puts
161
+ dict = {"\x57" => "Track 2 Equivalent Data",
162
+ "\x70" => "READ RECORD Response Message Template"}
163
+ puts TLV.parse_dgi_hex("010142704057134451973022158124D12102011089573110000F5F201A4D55535445524D414E4E2F4D41582020202020202020202020209F1F0B0102030405060708090A0B")
164
+ puts
165
+ puts TLV.parse_dgi_hex("010142704057134451973022158124D12102011089573110000F5F201A4D55535445524D414E4E2F4D41582020202020202020202020209F1F0B0102030405060708090A0B02018670818390818047D56F644D05FF41180926D965765BBC1894E6F973FA6DD56FC69313E82E9480F3405D7A4056B3AB5F31293D22F55A460D540E954BCF74E3D056DA839E756D1C6AC4BAD76D2747E158288BDE28CEEB321C930ED2F40ED35884304DD3D69E87BBC81FBEE22ACD2F0851A5DCA6DAAC794E633A70072AF5B93103C115B225118B77 ", dict)
166
+ end
167
+ #++