iso7816 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,108 @@
1
+ autoload :OpenSSL, 'openssl'
2
+
3
+ module EMV
4
+ module Crypto
5
+
6
+ # Generate Session keys according to CPS 5.3
7
+ def self.generate_session_key ic_card_key, seq, type
8
+ seq = check_and_convert("seq", seq, 2)
9
+ ic_card_key = check_and_convert("ic_card_key", ic_card_key, 16)
10
+
11
+ derivation_data = case type
12
+ when :enc
13
+ "\x01\x82"
14
+ when :mac
15
+ "\x01\x01"
16
+ when :dek
17
+ "\x01\x81"
18
+ else
19
+ raise "invalid type of key: #{type}"
20
+ end
21
+
22
+ derivation_data += seq + ISO7816.s2b("000000000000000000000000")
23
+
24
+ cipher = OpenSSL::Cipher::Cipher.new("des-ede-cbc").encrypt
25
+ cipher.key = ic_card_key
26
+ cipher.update derivation_data
27
+ end
28
+
29
+ # encrypt ecb according to CPS 5.5.1
30
+ def self.encrypt_ecb key, data
31
+ cipher = OpenSSL::Cipher::Cipher.new("des-ede").encrypt
32
+ cipher.key = key
33
+ cipher.update(data)
34
+ end
35
+
36
+ #encrypt cbc accroding to CPS 5.5.2
37
+ def self.encrypt_cbc key, data
38
+ cipher = OpenSSL::Cipher::Cipher.new("des-ede-cbc").encrypt
39
+ cipher.key = key
40
+ data = pad(data)
41
+ cipher.update data
42
+ end
43
+
44
+ # Mac calculation according to CPS 5.4.1.
45
+ def self.mac_for_personalization key, input
46
+ key = check_and_convert "key", key, 16
47
+ input = pad(input)
48
+ cipher = OpenSSL::Cipher::Cipher.new("des-ede-cbc").encrypt
49
+ cipher.key = key
50
+ mac = ""
51
+ input.scan(/.{8}/m) {|block|
52
+ mac = cipher.update block
53
+ }
54
+ mac
55
+ end
56
+
57
+ # calculate ISO 9797-1 "Retail Mac"
58
+ # (MAC Algorithm 3 with output transformation 3,
59
+ # without truncation) :
60
+ # DES with final TDES.
61
+ #
62
+ # Padding is added if data is not a multiple of 8
63
+ def self.retail_mac key, data
64
+ cipher = OpenSSL::Cipher::Cipher.new("des-cbc").encrypt
65
+ cipher.key = key[0,8]
66
+
67
+ data = pad(data)
68
+
69
+ single_data = data[0,data.length-8]
70
+ # Single DES with XOR til the last block
71
+ if single_data && single_data.length > 0
72
+ mac_ = cipher.update(single_data)
73
+ mac_ = mac_[mac_.length-8, 8]
74
+ else # length of data was <= 8
75
+ mac_ = "\x00"*8
76
+ end
77
+
78
+ triple_data = data[data.length-8, 8]
79
+ mac = ""
80
+ 0.upto(7) { |i|
81
+ mac << (mac_[i] ^ triple_data[i])
82
+ }
83
+ # Final Round of TDES
84
+ cipher = OpenSSL::Cipher::Cipher.new("des-ede").encrypt
85
+ cipher.key = key
86
+ cipher.update(mac)
87
+ end
88
+
89
+ def self.pad input
90
+
91
+ input += "\x80"
92
+ while (input.length % 8) != 0
93
+ input << 0x00
94
+ end
95
+ input
96
+ end
97
+
98
+ def self.check_and_convert mes, data, length_in_bytes
99
+ raise "`#{mes}` may not be nil" unless data
100
+ unless data.length == length_in_bytes || data.length == length_in_bytes*2
101
+ raise "invalid length for '#{mes}'. Should be #{length_in_bytes}"
102
+ end
103
+ data = ISO7816.s2b(data) if data.length == length_in_bytes*2
104
+ data
105
+ end
106
+
107
+ end #module Crypto
108
+ end #module EMV
@@ -0,0 +1,15 @@
1
+ require 'tlv'
2
+
3
+ module EMV
4
+ module Data
5
+ class InitializeUpdateData < TLV::TLV
6
+ b 6*8, "Identifier of the KMC", :kmc_id
7
+ b 4*8, "Chip Serial Number", :csn
8
+ b 8, "Version Number of Master key (KMC)", :kmc_version
9
+ b 8, "Identifier of Secure Channel Protocol", :sec_channel_proto_id
10
+ b 2*8, "Sequence Counter", :sequence_counter
11
+ b 6*8, "Card challenge (r card)", :challenge
12
+ b 8*8, "Card Cryptogram", :cryptogram
13
+ end
14
+ end #module DATA
15
+ end #module EMV
@@ -0,0 +1,5 @@
1
+
2
+ module EMV
3
+ # according to EMV 1, 12.2.2
4
+ PSE_DDF="1PAY.SYS.DDF01"
5
+ end
@@ -0,0 +1,56 @@
1
+ module EMV
2
+
3
+ module APDU
4
+
5
+ class EMV_APDU < ISO7816::APDU::APDU
6
+ cla "\x80"
7
+ end
8
+
9
+ def APDU.create_class name, ins, cla="80"
10
+ cl = "class #{name} < EMV_APDU\n"
11
+ cl << " cla \"\\x#{cla}\"\n" unless cla == "80"
12
+ cl << " ins \"\\x#{ins}\"\n"
13
+ cl << "end"
14
+
15
+ eval(cl)
16
+ end
17
+ [
18
+ ["APPLICATION_BLOCK", "1E", "84"],
19
+ ["APPLICATION_UNBLOCK", "18", "84"],
20
+ ["CARD_BLOCK", "16", "84"],
21
+ ["GENERATE_APPLICATION_CRYPTOGRAM", "AE"],
22
+ ["GET_DATA", "CA"],
23
+ ["GET_PROCESSING_OPTIONS", "A8"],
24
+ ["PIN_CHANGE_UNBLOCK", "24", "84"],
25
+ ].each{|entry|
26
+ APDU.create_class *entry
27
+ }
28
+
29
+
30
+ class EXTERNAL_AUTHENTICATE < ISO7816::APDU::EXTERNAL_AUTHENTICATE
31
+ end
32
+ class GET_CHALLENGE < ISO7816::APDU::GET_CHALLENGE
33
+ end
34
+ class INTERNAL_AUTHENTICATE < ISO7816::APDU::INTERNAL_AUTHENTICATE
35
+ end
36
+ class READ_RECORD < ISO7816::APDU::READ_RECORD
37
+ end
38
+ class SELECT < ISO7816::APDU::SELECT
39
+ p1 "\x04"
40
+ end
41
+ class VERIFY < ISO7816::APDU::VERIFY
42
+ def initialize card
43
+ super
44
+ self.plaintext_pin
45
+ end
46
+ def plaintext_pin
47
+ @p2 = ("" << 0x80)
48
+ end
49
+ def enciphered_pin
50
+ @p2 = ("" << 0x88)
51
+ end
52
+ end
53
+
54
+ end # APDU
55
+
56
+ end #EMV
@@ -0,0 +1,9 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require '7816/card'
5
+ require '7816/atr'
6
+ require '7816/apdu'
7
+ require '7816/iso_apdu'
8
+ require '7816/pcsc_helper'
9
+
@@ -0,0 +1,5 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'iso7816'
5
+
@@ -0,0 +1,63 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/iso7816'
3
+
4
+ class TestResponse < Test::Unit::TestCase
5
+
6
+ def setup
7
+ end
8
+
9
+ def test_resp_basics
10
+
11
+ response = ISO7816::APDU::Response
12
+
13
+ r = response.new "\x90\x00"
14
+ assert r.normal?
15
+ assert r.data == ""
16
+
17
+ r = response.new "blabla\x90\x00", 6
18
+ assert r.normal?
19
+
20
+
21
+ r = response.new "blabla\x61\xff", 6
22
+ assert r.normal?
23
+
24
+ ["\x62","\x63"].each {|byte|
25
+ r = response.new "bla#{byte}#{byte}", 3
26
+ assert r.warning?
27
+ }
28
+
29
+ ["\x64", "\x65", "\x66"].each_with_index {|byte, i|
30
+ r= response.new "#{'a'*i}#{byte}\x00", i
31
+ assert r.execution_err?
32
+ }
33
+
34
+ ["\x67", "\x68", "\x69", "\x6a", "\x6b", "\x6c", "\x6d", "\x6e", "\x6f"].each_with_index { |byte, i|
35
+ r= response.new "#{'a'*i}#{byte}\x00", i
36
+ assert r.checking_err?
37
+ assert r.data == 'a'*i
38
+ }
39
+ end
40
+
41
+ # these are a tad contrived and are only meant to ensure basic coverage...
42
+ def test_resp_to_s
43
+ response = ISO7816::APDU::Response
44
+ r = response.new "\x90\x00"
45
+ assert r.to_s == "APDU Response\n SW1: 90 (OK: No further qualification)\n SW2: 00 ()\n"
46
+
47
+ r = response.new "blabla\x61\xff"
48
+ assert r.to_s == "APDU Response\n SW1: 61 (OK: SW2 indicates the number of response bytes still available)\n SW2: ff (ff bytes remaining.)\n"
49
+
50
+ r = response.new "bla\x62\62"
51
+ assert r.to_s == "APDU Response\n SW1: 62 (WARN: State of non-volatile memory unchanged)\n SW2: 32 (unknown sw2 for sw1=62: 32)\n"
52
+
53
+ r = response.new "\x63\x81"
54
+ assert r.to_s == "APDU Response\n SW1: 63 (WARN: State of non-volatile memory changed)\n SW2: 81 (File filled up by the last write)\n"
55
+
56
+ r = response.new "#{"aa"}\x66\x00", 2
57
+ assert r.to_s == "APDU Response\n Data(2): 6161\n SW1: 66 (EXE ERR: Reserved for security-related issues)\n SW2: 00 (security (undefined))\n"
58
+
59
+ r = response.new "#{"aa"}\x69\x77", 2
60
+ assert r.to_s == "APDU Response\n Data(2): 6161\n SW1: 69 (CHK ERR: Command not allowed)\n SW2: 77 (unknown sw2 for sw1=69: 77)\n"
61
+
62
+ end
63
+ end
@@ -0,0 +1,128 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/iso7816'
3
+
4
+ class TestAPDU < Test::Unit::TestCase
5
+
6
+ def setup
7
+ end
8
+
9
+ def s2b str
10
+ ISO7816.s2b str
11
+ end
12
+
13
+ def test_select
14
+ s = ISO7816::APDU::SELECT.new
15
+
16
+ assert_equal "\x00\xa4\x00\x00", s.to_b
17
+ assert_equal "\n|CLA|INS| P1| P2|\n| 00| a4| 00| 00|", s.to_s
18
+
19
+ # add filename
20
+
21
+ s.data = "\x37\x00"
22
+ assert_equal "\x00\xa4\x00\x00\x02\x37\x00", s.to_b
23
+ assert_equal "\n|CLA|INS| P1| P2|| LC|Data| LE|\n| 00| a4| 00| 00|| 02|3700| |", s.to_s
24
+ end
25
+
26
+ def test_invalid_ins
27
+ apdu = ISO7816::APDU::APDU.new
28
+ ["\x90", "\x95", "\x9f", "\x60", "\x65", "\x6f"]. each {|inv|
29
+ apdu.ins=inv
30
+ assert_equal false, apdu.ins_valid?
31
+ }
32
+ ["\x10", "\x25", "\x3f", "\x40", "\x55", "\xff"]. each {|inv|
33
+ apdu.ins=inv
34
+ assert_equal true, apdu.ins_valid?
35
+ }
36
+ end
37
+
38
+ def test_basics
39
+ a = ISO7816::APDU::APDU.new "test-card"
40
+ assert_equal "\x00", a.cla
41
+ assert_equal "\x00", a.ins
42
+ assert_equal "\x00", a.p1
43
+ assert_equal "\x00", a.p2
44
+ assert_equal "", a.data
45
+ assert_equal "", a.le
46
+ assert_equal "test-card", a.card
47
+
48
+ assert_nothing_raised {
49
+ a.to_s
50
+ a.to_b
51
+ }
52
+
53
+ assert_equal false, a.case_4?
54
+ a.data = "abc"
55
+ a.le = 4
56
+ assert_equal true, a.case_4?
57
+
58
+ a = ISO7816::APDU::RandomAPDU.new "test-card"
59
+ [a.cla, a.ins, a.p1, a.p2].each {|by|
60
+ assert_not_equal nil, by
61
+ assert_equal 1, by.length
62
+ }
63
+ assert_equal true, a.ins_valid?
64
+
65
+ assert_equal "", a.data
66
+ assert_equal "test-card", a.card
67
+
68
+ assert_nothing_raised {
69
+ a.to_s
70
+ a.to_b
71
+ }
72
+ end
73
+ def test_to_apdu
74
+ apdu = ISO7816::APDU::APDU.to_apdu(s2b("80b4000008"))
75
+ assert_equal "\x80", apdu.cla
76
+ assert_equal "\xb4", apdu.ins
77
+ assert_equal "\x00", apdu.p1
78
+ assert_equal "\x00", apdu.p2
79
+ assert_equal "\x08", apdu.le
80
+
81
+ apdu = ISO7816::APDU::APDU.to_apdu(s2b("8020000008aaaaaaaaaaaaaaaa"))
82
+ assert_equal "\x80", apdu.cla
83
+ assert_equal "\x20", apdu.ins
84
+ assert_equal "\x00", apdu.p1
85
+ assert_equal "\x00", apdu.p2
86
+ assert_equal "\x08", apdu.lc
87
+ assert_equal "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", apdu.data
88
+
89
+ assert_raise(RuntimeError){
90
+ apdu = ISO7816::APDU::APDU.to_apdu(s2b("8020000008aaaaaaaaaaaaaaaaaaaa"))
91
+ }
92
+
93
+ apdu = ISO7816::APDU::APDU.to_apdu(s2b("fefefefefe"))
94
+ assert_equal "\xfe", apdu.cla
95
+ assert_equal "\xfe", apdu.ins
96
+ assert_equal "\xfe", apdu.p1
97
+ assert_equal "\xfe", apdu.p2
98
+ assert_equal "\xfe", apdu.le
99
+ assert_equal "", apdu.lc
100
+ assert_equal "", apdu.data
101
+
102
+ end
103
+ def test_get_response
104
+ gr = ISO7816::APDU::GET_RESPONSE.new "dummy-card"
105
+ assert_equal "dummy-card", gr.card
106
+ assert_equal "\xc0", gr.ins
107
+ assert_equal "\x00", gr.cla
108
+ end
109
+
110
+ class DingDong < ISO7816::APDU::APDU
111
+ cla "\x01"
112
+ ins "\x02"
113
+ p1 "\x03"
114
+ p2 "\x04"
115
+ le "\x05"
116
+ end
117
+ def test_meta_class
118
+ assert_equal "\x01", DingDong._cla
119
+ d = DingDong.new
120
+ assert_equal "\x01", d.cla
121
+ assert_equal "\x05", d.le
122
+ to_strinq=%Q{
123
+ |CLA|INS| P1| P2|| LC|Data| LE|
124
+ | 01| 02| 03| 04|| | | 05|}
125
+ assert_equal to_strinq, d.to_s
126
+ end
127
+
128
+ end
@@ -0,0 +1,36 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/iso7816'
3
+
4
+ class TestCard < Test::Unit::TestCase
5
+
6
+ ATR = "\x3b\xd9\x18\x00\x00\x80\x54\x43\x4f\x4c\x44\x82\x90\x00"
7
+ def setup
8
+ @server = Thread.start {
9
+ require 'socket'
10
+ ssock = TCPServer.open(1024)
11
+ while (s = ssock.accept)
12
+ s.send ATR, 0
13
+ end #while
14
+ ssock.close
15
+ }
16
+ end
17
+
18
+ def teardown
19
+ @server.exit
20
+ end
21
+
22
+ def test_connect
23
+ card = ISO7816::Card::TCPCard.new
24
+ atr = card.connect
25
+ assert_equal true, card.connected?
26
+ assert_equal ATR, atr
27
+ card.disconnect
28
+ assert_equal false, card.connected?
29
+
30
+ atr = card.connect {}
31
+ assert_equal ATR, atr
32
+ assert_equal false, card.connected?
33
+
34
+
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/iso7816'
3
+
4
+ class TestAPDU < Test::Unit::TestCase
5
+
6
+ def setup
7
+ end
8
+
9
+ def s2b str
10
+ ISO7816.s2b str
11
+ end
12
+
13
+ def test_store_data_security_level
14
+ mock_ctx = Object.new
15
+ mock_ctx.class.class_eval{
16
+ attr_accessor :level
17
+ }
18
+
19
+ [:no_sec, :mac, :enc_and_mac].each {|lev|
20
+ mock_ctx.level=lev
21
+ sd = EMV::APDU::CPS::STORE_DATA.new nil, mock_ctx
22
+ # shouldn't be set
23
+ assert_equal nil, sd.security_level
24
+ assert !sd.secure?
25
+
26
+ sd.security_level = :no_sec
27
+ assert !sd.secure?
28
+ sd.security_level = :mac
29
+ assert sd.secure?
30
+ sd.security_level = :enc_and_mac
31
+ assert sd.secure?
32
+ }
33
+ assert true
34
+ end
35
+ end