tlv 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ #++