iso7816 0.0.3

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