pkcs11 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,90 @@
1
+ module PKCS11
2
+ # Each slot corresponds to a physical reader or other device interface.
3
+ # It may contain a token.
4
+ class Slot
5
+ def initialize(pkcs11, slot) # :nodoc:
6
+ @pk, @slot = pkcs11, slot
7
+ end
8
+
9
+ # The slot handle.
10
+ def to_int
11
+ @slot
12
+ end
13
+ alias to_i to_int
14
+
15
+ def inspect # :nodoc:
16
+ "#<#{self.class} #{@slot.inspect}>"
17
+ end
18
+
19
+ # Obtains information about a particular slot in the system.
20
+ def C_GetSlotInfo
21
+ @pk.C_GetSlotInfo(@slot)
22
+ end
23
+ alias info C_GetSlotInfo
24
+
25
+ # Obtains information about a particular token in the system.
26
+ def C_GetTokenInfo
27
+ @pk.C_GetTokenInfo(@slot)
28
+ end
29
+ alias token_info C_GetTokenInfo
30
+
31
+ # Waits for a slot event, such as token insertion or token removal, to
32
+ # occur. flags determines whether or not the C_WaitForSlotEvent call blocks (i.e., waits
33
+ # for a slot event to occur);
34
+ def C_WaitForSlotEvent(flags)
35
+ @pk.C_WaitForSlotEvent(@slot, flags)
36
+ end
37
+ alias wait_for_event C_WaitForSlotEvent
38
+
39
+ # C_GetMechanismList is used to obtain a list of mechanism types supported by a token.
40
+ def C_GetMechanismList
41
+ @pk.C_GetMechanismList(@slot).map{|mech|
42
+ Mechanism.new MECHANISMS, mech
43
+ }
44
+ end
45
+ alias mechanisms C_GetMechanismList
46
+
47
+ # Obtains information about a particular mechanism possibly
48
+ # supported by a token.
49
+ def C_GetMechanismInfo(mechanism)
50
+ @pk.C_GetMechanismInfo(@slot, Session.hash_to_mechanism(mechanism))
51
+ end
52
+ alias mechanism_info C_GetMechanismInfo
53
+
54
+ # Initializes a token. pin is the SO’s initial PIN; label is the label of the token (max 32-byte). This standard allows PIN
55
+ # values to contain any valid UTF8 character, but the token may impose subset restrictions.
56
+ def C_InitToken(pin, label)
57
+ @pk.C_InitToken(@slot, pin, label.ljust(32, " "))
58
+ end
59
+ alias init_token C_InitToken
60
+
61
+ # Opens a Session between an application and a token in a particular slot.
62
+ #
63
+ # flags:: indicates the type of session. Default is read-only,
64
+ # use <tt>CKF_SERIAL_SESSION | CKF_RW_SESSION</tt> for read-write session.
65
+ #
66
+ # * If called with block, yields the block with the session and closes the session
67
+ # when the is finished.
68
+ # * If called without block, returns the session object.
69
+ def C_OpenSession(flags=CKF_SERIAL_SESSION)
70
+ nr = @pk.C_OpenSession(@slot, flags)
71
+ sess = Session.new @pk, nr
72
+ if block_given?
73
+ begin
74
+ yield sess
75
+ ensure
76
+ sess.close
77
+ end
78
+ else
79
+ sess
80
+ end
81
+ end
82
+ alias open C_OpenSession
83
+
84
+ # Closes all sessions an application has with a token.
85
+ def C_CloseAllSessions
86
+ @pk.C_CloseAllSessions(@slot)
87
+ end
88
+ alias close_all_sessions C_CloseAllSessions
89
+ end
90
+ end
@@ -0,0 +1,90 @@
1
+ require "pkcs11"
2
+ require "openssl"
3
+
4
+ LIBSOFTOKEN3_SO = "libsoftokn3.so"
5
+ LIBNSS_PATHS = %w(
6
+ /usr/lib64 /usr/lib/ /usr/lib64/nss /usr/lib/nss
7
+ )
8
+ unless so_path = ARGV.shift
9
+ paths = LIBNSS_PATHS.collect{|path| File.join(path, LIBSOFTOKEN3_SO) }
10
+ so_path = paths.find{|path| File.exist?(path) }
11
+ end
12
+
13
+ dir = Dir.glob(File.expand_path("~/.mozilla/firefox/*.default")).first
14
+ NSS_INIT_ARGS = [
15
+ "configDir='#{dir}'",
16
+ "secmod='secmod.db'",
17
+ "flags='readOnly'",
18
+ ]
19
+
20
+ args = PKCS11::CK_C_INITIALIZE_ARGS.new
21
+ args.flags = 0
22
+ args.pReserved = NSS_INIT_ARGS.join(" ")
23
+
24
+ pk11 = PKCS11.new(so_path, args)
25
+ info = pk11.C_GetInfo
26
+ p [
27
+ info.cryptokiVersion, info.manufacturerID, info.flags,
28
+ info.libraryDescription, info.libraryVersion
29
+ ]
30
+
31
+ slots = pk11.C_GetSlotList(false)
32
+ p slots
33
+
34
+ slot = 2
35
+ sinfo = pk11.C_GetSlotInfo(slot)
36
+ p [
37
+ sinfo.slotDescription, sinfo.manufacturerID, sinfo.flags,
38
+ sinfo.hardwareVersion, sinfo.firmwareVersion
39
+ ]
40
+ mechanisms = pk11.C_GetMechanismList(slot)
41
+ mechanisms.each do |m|
42
+ p PKCS11::MECHANISMS[m] || m
43
+ end
44
+
45
+ flags = PKCS11::CKF_SERIAL_SESSION | PKCS11::CKF_RW_SESSION
46
+ session = pk11.C_OpenSession(slot, flags)
47
+ p [:session, session]
48
+ pk11.C_Login(session, PKCS11::CKU_USER, "")
49
+
50
+ find_template = [
51
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_CLASS, PKCS11::CKO_CERTIFICATE),
52
+ ]
53
+ p pk11.C_FindObjectsInit(session, find_template)
54
+ objs = pk11.C_FindObjects(session, 128)
55
+ objs.each do |handle|
56
+ template = [
57
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_SUBJECT, nil),
58
+ ]
59
+ attrs = pk11.C_GetAttributeValue(session, handle, template)
60
+ attrs.each do |attr|
61
+ p OpenSSL::X509::Name.new(attr.value)
62
+ end
63
+ end
64
+ objs = pk11.C_FindObjectsFinal(session)
65
+
66
+ find_template = [
67
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_CLASS, PKCS11::CKO_PRIVATE_KEY),
68
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_KEY_TYPE, PKCS11::CKK_RSA),
69
+ ]
70
+ p pk11.C_FindObjectsInit(session, find_template)
71
+ objs = pk11.C_FindObjects(session, 128)
72
+ objs.each do |handle|
73
+ template = [
74
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_CLASS, nil),
75
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_KEY_TYPE, nil),
76
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_ID, nil),
77
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_SIGN, nil),
78
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_SIGN_RECOVER, nil),
79
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_DECRYPT, nil),
80
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_EXTRACTABLE, nil),
81
+ ]
82
+ attrs = pk11.C_GetAttributeValue(session, handle, template)
83
+ attrs.each do |attr|
84
+ p [PKCS11::ATTRIBUTES[attr.type], attr.value]
85
+ end
86
+ end
87
+ objs = pk11.C_FindObjectsFinal(session)
88
+
89
+ pk11.C_Logout(session)
90
+ pk11.C_CloseSession(session)
data/sample/nssckbi.rb ADDED
@@ -0,0 +1,51 @@
1
+ require "pkcs11"
2
+ require "openssl"
3
+
4
+ LIBNSSCKBI_SO = "libnssckbi.so"
5
+ LIBNSS_PATHS = %w(
6
+ /usr/lib64 /usr/lib /usr/lib64/nss /usr/lib/nss
7
+ /usr/lib64/xulrunner /usr/lib/xulrunner
8
+ /usr/local/lib64/xulrunner /usr/local/lib/xulrunner
9
+ )
10
+ unless so_name = ARGV[0]
11
+ paths = LIBNSS_PATHS.collect{|path| File.join(path, LIBNSSCKBI_SO) }
12
+ so_name = paths.find{|path| File.exist?(path) }
13
+ end
14
+
15
+ pkcs11 = PKCS11.new(so_name)
16
+ slot = pkcs11.C_GetSlotList(true).first
17
+ session = pkcs11.C_OpenSession(slot, PKCS11::CKF_SERIAL_SESSION)
18
+
19
+ pkcs11.C_FindObjectsInit(session, [
20
+ PKCS11::CK_ATTRIBUTE.new(PKCS11::CKA_CLASS, PKCS11::CKO_CERTIFICATE)
21
+ ])
22
+ handles = pkcs11.C_FindObjects(session, 1000)
23
+ pkcs11.C_FindObjectsFinal(session)
24
+
25
+ attribute_types = [
26
+ PKCS11::CKA_CLASS,
27
+ PKCS11::CKA_TOKEN, PKCS11::CKA_PRIVATE, PKCS11::CKA_MODIFIABLE,
28
+ PKCS11::CKA_LABEL, PKCS11::CKA_CERTIFICATE_TYPE,
29
+ PKCS11::CKA_SUBJECT, PKCS11::CKA_ID, PKCS11::CKA_ISSUER,
30
+ PKCS11::CKA_SERIAL_NUMBER, PKCS11::CKA_VALUE,
31
+ ]
32
+ template = attribute_types.collect{|a| PKCS11::CK_ATTRIBUTE.new(a, nil) }
33
+ handles.each do |handle|
34
+ attributes = pkcs11.C_GetAttributeValue(session, handle, template)
35
+ attributes.each do |attribute|
36
+ type_name = PKCS11::ATTRIBUTES[attribute.type]
37
+ case attribute.type
38
+ when PKCS11::CKA_LABEL
39
+ p [type_name, attribute.value]
40
+ when PKCS11::CKA_SUBJECT, PKCS11::CKA_ISSUER
41
+ p [type_name, OpenSSL::X509::Name.new(attribute.value)]
42
+ when PKCS11::CKA_SERIAL_NUMBER
43
+ serial = OpenSSL::ASN1.decode(attribute.value).value rescue nil
44
+ attribute.value.unpack("w").first
45
+ p [type_name, serial]
46
+ when PKCS11::CKA_VALUE
47
+ cert = OpenSSL::X509::Certificate.new(attribute.value)
48
+ p [cert.serial, cert.not_before, cert.not_after]
49
+ end
50
+ end
51
+ end
Binary file
Binary file
Binary file
data/test/helper.rb ADDED
@@ -0,0 +1,43 @@
1
+ require "openssl"
2
+
3
+ def open_softokn
4
+
5
+ if RUBY_PLATFORM =~ /win32/
6
+ lLIBSOFTOKEN3_SO = "softokn3.dll"
7
+
8
+ # Try to find the firefox path.
9
+ unless ENV['SOFTOKN_PATH']
10
+ require 'win32/registry'
11
+ begin
12
+ firefox_path = Win32::Registry::HKEY_LOCAL_MACHINE.open('SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\firefox.exe'){|reg|
13
+ reg.read('Path')[1]
14
+ }
15
+ rescue Win32::Registry::Error
16
+ end
17
+ if firefox_path
18
+ ENV['Path'] = ENV['Path'] + ";" + firefox_path
19
+ so_path = File.join(firefox_path, lLIBSOFTOKEN3_SO)
20
+ end
21
+ end
22
+ else
23
+ lLIBSOFTOKEN3_SO = "libsoftokn3.so"
24
+ lLIBNSS_PATHS = %w(
25
+ /usr/lib64 /usr/lib/ /usr/lib64/nss /usr/lib/nss
26
+ )
27
+ unless so_path = ENV['SOFTOKN_PATH']
28
+ paths = lLIBNSS_PATHS.collect{|path| File.join(path, lLIBSOFTOKEN3_SO) }
29
+ so_path = paths.find{|path| File.exist?(path) }
30
+ end
31
+ end
32
+
33
+ raise "#{lLIBSOFTOKEN3_SO} not found - please install firefox or set ENV['SOFTOKN_PATH']" unless so_path
34
+
35
+ dir = File.join(File.dirname(__FILE__), 'fixtures/softokn')
36
+ nNSS_INIT_ARGS = [
37
+ "configDir='#{dir}'",
38
+ "secmod='secmod.db'",
39
+ "flags='readWrite'",
40
+ ]
41
+
42
+ pk11 = PKCS11.open(so_path, :flags=>0, :pReserved=>nNSS_INIT_ARGS.join(" "))
43
+ end
@@ -0,0 +1,36 @@
1
+ require "test/unit"
2
+ require "pkcs11"
3
+ require "test/helper"
4
+
5
+ class TestPkcs11 < Test::Unit::TestCase
6
+ def setup
7
+ @pk = open_softokn
8
+ end
9
+
10
+ def teardown
11
+ @pk.close
12
+ @pk = nil
13
+ GC.start
14
+ end
15
+
16
+ def pk
17
+ @pk
18
+ end
19
+
20
+ def test_info
21
+ info = pk.info
22
+ assert info.inspect =~ /cryptokiVersion=/, 'There should be a version in the library info'
23
+ end
24
+
25
+ def test_slots
26
+ slots = pk.active_slots
27
+ assert slots.length>=1, 'Hope there is at least one active slot'
28
+ end
29
+
30
+ def test_close
31
+ pk.close
32
+ assert_raise(PKCS11::Error){ pk.info }
33
+ pk.close
34
+ assert_raise(PKCS11::Error){ pk.active_slots }
35
+ end
36
+ end
@@ -0,0 +1,167 @@
1
+ require "test/unit"
2
+ require "pkcs11"
3
+ require "test/helper"
4
+ require "openssl"
5
+
6
+ class TestPkcs11Crypt < Test::Unit::TestCase
7
+ include PKCS11
8
+
9
+ attr_reader :slots
10
+ attr_reader :slot
11
+ attr_reader :session
12
+ attr_reader :rsa_priv_key
13
+ attr_reader :rsa_pub_key
14
+ attr_reader :secret_key
15
+
16
+ def setup
17
+ $pkcs11 ||= open_softokn
18
+ @slots = pk.active_slots
19
+ @slot = slots.last
20
+ @session = slot.open
21
+ session.login(:USER, "")
22
+
23
+ @rsa_pub_key = session.find_objects(:CLASS => CKO_PUBLIC_KEY,
24
+ :KEY_TYPE => CKK_RSA).first
25
+ @rsa_priv_key = session.find_objects(:CLASS => CKO_PRIVATE_KEY,
26
+ :KEY_TYPE => CKK_RSA).first
27
+ @secret_key = session.create_object(
28
+ :CLASS=>CKO_SECRET_KEY,
29
+ :KEY_TYPE=>CKK_DES2,
30
+ :ENCRYPT=>true, :WRAP=>true, :DECRYPT=>true, :UNWRAP=>true, :TOKEN=>false,
31
+ :VALUE=>'0123456789abcdef',
32
+ :LABEL=>'test_secret_key')
33
+ end
34
+
35
+ def teardown
36
+ @secret_key.destroy
37
+ @session.logout
38
+ @session.close
39
+ end
40
+
41
+ def pk
42
+ $pkcs11
43
+ end
44
+
45
+ def test_endecrypt
46
+ plaintext1 = "secret text"
47
+ cryptogram = session.encrypt( :RSA_PKCS, rsa_pub_key, plaintext1)
48
+ assert cryptogram.length>10, 'The cryptogram should contain some data'
49
+ assert_not_equal cryptogram, plaintext1, 'The cryptogram should be different to plaintext'
50
+
51
+ plaintext2 = session.decrypt( :RSA_PKCS, rsa_priv_key, cryptogram)
52
+ assert_equal plaintext1, plaintext2, 'Decrypted plaintext should be the same'
53
+ end
54
+
55
+ def test_sign_verify
56
+ plaintext = "important text"
57
+ signature = session.sign( :SHA1_RSA_PKCS, rsa_priv_key, plaintext)
58
+ assert signature.length>10, 'The signature should contain some data'
59
+
60
+ signature2 = session.sign( :SHA1_RSA_PKCS, rsa_priv_key){|c|
61
+ c.update(plaintext[0..3])
62
+ c.update(plaintext[4..-1])
63
+ }
64
+ assert_equal signature, signature2, 'results of one-step and two-step signatures should be equal'
65
+
66
+ valid = session.verify( :SHA1_RSA_PKCS, rsa_pub_key, signature, plaintext)
67
+ assert valid, 'The signature should be correct'
68
+
69
+ assert_raise(PKCS11::Error, 'The signature should be invalid on different text') do
70
+ session.verify( :SHA1_RSA_PKCS, rsa_pub_key, signature, "modified text")
71
+ end
72
+ end
73
+
74
+ def create_openssl_cipher(pk11_key)
75
+ rsa = OpenSSL::PKey::RSA.new
76
+ rsa.n = OpenSSL::BN.new pk11_key[:MODULUS], 2
77
+ rsa.e = OpenSSL::BN.new pk11_key[:PUBLIC_EXPONENT], 2
78
+ rsa
79
+ end
80
+
81
+ def test_compare_sign_with_openssl
82
+ signature = session.sign( :SHA1_RSA_PKCS, rsa_priv_key, "important text")
83
+
84
+ osslc = create_openssl_cipher rsa_pub_key
85
+ valid = osslc.verify(OpenSSL::Digest::SHA1.new, signature, "important text")
86
+ assert valid, 'The signature should be correct'
87
+ end
88
+
89
+ def test_compare_endecrypt_with_openssl
90
+ plaintext1 = "secret text"
91
+ osslc = create_openssl_cipher rsa_pub_key
92
+ cryptogram = osslc.public_encrypt(plaintext1)
93
+
94
+ plaintext2 = session.decrypt( :RSA_PKCS, rsa_priv_key, cryptogram)
95
+ assert_equal plaintext1, plaintext2, 'Decrypted plaintext should be the same'
96
+ end
97
+
98
+ def test_digest
99
+ plaintext = "secret text"
100
+ digest1 = session.digest( :SHA_1, plaintext)
101
+ digest2 = OpenSSL::Digest::SHA1.new(plaintext).digest
102
+ assert_equal digest1, digest2, 'Digests should be equal'
103
+ digest3 = session.digest(:SHA_1){|c|
104
+ c.update(plaintext[0..3])
105
+ c.update(plaintext[4..-1])
106
+ }
107
+ assert_equal digest1, digest3, 'Digests should be equal'
108
+
109
+ digest3 = session.digest(:SHA256){|c|
110
+ c.update(plaintext)
111
+ c.digest_key(secret_key)
112
+ }
113
+ end
114
+
115
+ def test_wrap_key
116
+ wrapped_key_value = session.wrap_key(:DES3_ECB, secret_key, secret_key)
117
+ assert_equal 16, wrapped_key_value.length, '112 bit 3DES key should have same size wrapped'
118
+
119
+ unwrapped_key = session.unwrap_key(:DES3_ECB, secret_key, wrapped_key_value, :CLASS=>CKO_SECRET_KEY, :KEY_TYPE=>CKK_DES2, :ENCRYPT=>true, :DECRYPT=>true)
120
+
121
+ secret_key_kcv = session.encrypt( :DES3_ECB, secret_key, "\0"*8)
122
+ unwrapped_key_kcv = session.encrypt( :DES3_ECB, unwrapped_key, "\0"*8)
123
+ assert_equal secret_key_kcv, unwrapped_key_kcv, 'Key check values of original and wrapped/unwrapped key should be equal'
124
+ end
125
+
126
+ def test_wrap_private_key
127
+ wrapped_key_value = session.wrap_key({:DES3_CBC_PAD=>"\0"*8}, secret_key, rsa_priv_key)
128
+ assert wrapped_key_value.length>100, 'RSA private key should have bigger size wrapped'
129
+ end
130
+
131
+ def test_generate_secret_key
132
+ key = session.generate_key(:DES2_KEY_GEN,
133
+ {:ENCRYPT=>true, :WRAP=>true, :DECRYPT=>true, :UNWRAP=>true, :TOKEN=>false, :LOCAL=>true})
134
+ assert_equal true, key[:LOCAL], 'Keys created on the token should be marked as local'
135
+ end
136
+
137
+ def test_generate_key_pair
138
+ pub_key, priv_key = session.generate_key_pair(:RSA_PKCS_KEY_PAIR_GEN,
139
+ {:ENCRYPT=>true, :VERIFY=>true, :WRAP=>true, :MODULUS_BITS=>768, :PUBLIC_EXPONENT=>[3].pack("N"), :TOKEN=>false},
140
+ {:PRIVATE=>true, :SUBJECT=>'test', :ID=>[123].pack("n"),
141
+ :SENSITIVE=>true, :DECRYPT=>true, :SIGN=>true, :UNWRAP=>true, :TOKEN=>false, :LOCAL=>true})
142
+
143
+ assert_equal true, priv_key[:LOCAL], 'Private keys created on the token should be marked as local'
144
+ assert_equal priv_key[:CLASS], CKO_PRIVATE_KEY
145
+ assert_equal pub_key[:CLASS], CKO_PUBLIC_KEY
146
+ assert_equal true, priv_key[:SENSITIVE], 'Private key should be sensitive'
147
+ end
148
+
149
+ def test_derive_key
150
+ # Generate DH key for side 1
151
+ key1 = OpenSSL::PKey::DH.new(512)
152
+
153
+ # Generate key side 2 with same prime and base as side 1
154
+ pub_key2, priv_key2 = session.generate_key_pair(:DH_PKCS_KEY_PAIR_GEN,
155
+ {:PRIME=>key1.p.to_s(2), :BASE=>key1.g.to_s(2), :TOKEN=>false},
156
+ {:VALUE_BITS=>512, :DERIVE=>true, :TOKEN=>false})
157
+
158
+ # Derive secret DES key for side 1 with OpenSSL
159
+ new_key1 = key1.compute_key(OpenSSL::BN.new pub_key2[:VALUE], 2)
160
+
161
+ # Derive secret DES key for side 2 with softokn3
162
+ new_key2 = session.derive_key( {:DH_PKCS_DERIVE=>key1.pub_key.to_s(2)}, priv_key2,
163
+ :CLASS=>CKO_SECRET_KEY, :KEY_TYPE=>CKK_AES, :VALUE_LEN=>16, :ENCRYPT=>true, :DECRYPT=>true, :SENSITIVE=>false )
164
+
165
+ assert_equal new_key1[0,16], new_key2[:VALUE], 'Exchanged session key should be equal'
166
+ end
167
+ end