diff --git a/Rakefile b/Rakefile
index dccdccd..1990cf5 100644
--- a/Rakefile
+++ b/Rakefile
@@ -65,7 +65,7 @@ file 'ext/pk11.h' => 'ext/pk11_thread_funcs.h'
desc "Generate static HTML documentation with YARD"
task :yardoc do
- sh "yardoc"
+ sh "yardoc --title \"PKCS#11/Ruby Interface\" --no-private lib/**/*.rb ext/*.c ext/*.doc pkcs11-safenet/lib/**/*.rb pkcs11-safenet/ext/*.c pkcs11-safenet/ext/*.doc"
end
desc "Publish YARD to wherever you want."
diff --git a/pkcs11-safenet/.yardopts b/pkcs11-safenet/.yardopts
new file mode 100644
index 0000000..952f261
--- /dev/null
+++ b/pkcs11-safenet/.yardopts
@@ -0,0 +1 @@
+--title "PKCS#11-Safenet/Ruby Interface" --no-private lib/**/*.rb ext/*.c ext/*.doc
diff --git a/pkcs11-safenet/Manifest.txt b/pkcs11-safenet/Manifest.txt
new file mode 100644
index 0000000..da563d8
--- /dev/null
+++ b/pkcs11-safenet/Manifest.txt
@@ -0,0 +1,14 @@
+.yardopts
+Manifest.txt
+README.rdoc
+Rakefile
+ext/Makefile
+ext/extconf.rb
+ext/generate_constants.rb
+ext/generate_structs.rb
+ext/pk11s.c
+lib/pkcs11_safenet.rb
+lib/pkcs11_safenet/extensions.rb
+test/helper.rb
+test/test_pkcs11_safenet.rb
+test/test_pkcs11_safenet_crypt.rb
diff --git a/pkcs11-safenet/README.rdoc b/pkcs11-safenet/README.rdoc
new file mode 100644
index 0000000..4f19ce0
--- /dev/null
+++ b/pkcs11-safenet/README.rdoc
@@ -0,0 +1,89 @@
+= PKCS #11/Ruby Interface for Safenet Protect Server HSM
+
+* Homepage: http://github.com/larskanis/pkcs11-safenet
+* API documentation: http://pkcs11.rubyforge.org/pkcs11-safenet/
+* API documentation of Ruby-PKCS#11: http://pkcs11.rubyforge.org/pkcs11/
+* Safenet[http://www.safenet-inc.com] - Protect Server HSM
+
+This ruby gem is an add-on to ruby-pkcs11[http://github.com/larskanis/pkcs11] .
+It allowes to use Protect Server specific extensions, which are beyond the PKCS#11 standard.
+That means CKA_EXPORT, CKM_DES3_DERIVE_CBC, structs like CK_DES3_CBC_PARAMS, special functions and so on.
+The module works on the Unix like operating systems and win32.
+
+== Requirements
+
+* Safenet PTKC-SDK to compile the module
+* pkcs11 gem installed (use: gem install pkcs11 )
+
+== Installation
+
+ gem install pkcs11-safenet -- --with-safenet-sdk-dir=/path/to/ETcpsdk
+
+This installs the Safenet-PKCS#11 extension along with pkcs11-gem either by compiling (Unix)
+or by using the precompiled gem for Win32.
+
+ git clone git://github.com/larskanis/pkcs11-safenet.git
+ cd pkcs11-safenet
+ rake gem SAFENET_SDK_DIR=/path/to/ETcpsdk
+ gem install -l pkg/pkcs11-safenet -- --with-safenet-sdk-dir=/path/to/ETcpsdk
+
+Downloads and installs the gem from git source.
+
+== Usage
+
+Open the software emulation library and login to a session:
+
+ require "rubygems"
+ require "pkcs11_safenet"
+
+ pkcs11 = PKCS11::Safenet::Library.new(:sw)
+ p pkcs11.info
+ session = pkcs11.active_slots.last.open
+ session.login(:USER, "1234")
+ # ... crypto operations
+ session.logout
+ session.close
+
+{PKCS11::Safenet::Library#initialize} tries to find the library file in
+the standard installation directory on Windows or Linux.
+
+== Cross compiling for mswin32
+
+Using rake-compiler a cross compiled pkcs11-safenet-gem can be build on a linux host for
+the win32 platform. There are no runtime dependencies to any but the standard Windows DLLs.
+
+Install mingw32. On a debian based system this should work:
+
+ apt-get install mingw32
+
+On MacOS X, if you have MacPorts installed:
+
+ port install i386-mingw32-gcc
+
+Install the rake-compiler:
+
+ gem install rake-compiler
+
+Download and cross compile ruby for win32:
+
+ rake-compiler cross-ruby VERSION=1.8.6-p287
+
+Download and cross compile pkcs11-safenet for win32:
+
+ rake cross native gem SAFENET_SDK_DIR=/path/to/ETcpsdk
+
+If everything works, there should be pkcs11-safenet-VERSION-x86-mswin32.gem in the pkg
+directory.
+
+
+== ToDo
+
+* implement Safenet specific function calls
+* implement possibility to use callbacks
+* add all structs and constants
+
+== Authors
+* Lars Kanis
+
+== Copying
+See MIT-LICENSE included in the package.
diff --git a/pkcs11-safenet/Rakefile b/pkcs11-safenet/Rakefile
new file mode 100644
index 0000000..be826cf
--- /dev/null
+++ b/pkcs11-safenet/Rakefile
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+# -*- ruby -*-
+
+require 'rubygems'
+require 'hoe'
+require 'rake/extensiontask'
+require 'rbconfig'
+
+SAFENET_SDK_DIR = ENV['SAFENET_SDK_DIR'] || '/opt/ETcpsdk'
+RUBY_PKCS11_EXT_DIR = File.expand_path('../ext')
+
+CLEAN.include 'ext/pk11s_struct_def.inc'
+CLEAN.include 'ext/pk11s_struct_impl.inc'
+CLEAN.include 'ext/pk11s_const_def.inc'
+CLEAN.include 'lib/pkcs11_ext.so'
+CLEAN.include 'tmp'
+
+def pkcs11_version
+ file = File.join(RUBY_PKCS11_EXT_DIR, 'pk11_version.h')
+ version_re = /VERSION += +([\"\'])([\d][\d\w\.]+)\1/
+ File.read_utf(file)[version_re, 2]
+end
+
+hoe = Hoe.spec 'pkcs11-safenet' do
+ developer('Lars Kanis', 'kanis@comcard.de')
+ extra_deps << ['pkcs11', "= #{pkcs11_version}"]
+ extra_dev_deps << ['yard', '>= 0.6']
+ extra_dev_deps << ['rake-compiler', '>= 0.7']
+
+ self.url = 'http://github.com/larskanis/pkcs11-safenet'
+ self.summary = 'Safenet extensions for PKCS#11-Ruby'
+ self.description = 'This module allows Ruby programs to use vendor extensions for Safenet Protect Server.'
+ self.version = pkcs11_version
+
+ self.readme_file = 'README.rdoc'
+ self.history_file = '../History.txt'
+ self.extra_rdoc_files << self.readme_file << 'ext/pk11s.c'
+ spec_extras[:extensions] = 'ext/extconf.rb'
+ spec_extras[:files] = File.read_utf("Manifest.txt").split(/\r?\n\r?/)
+ spec_extras[:files] << 'ext/pk11s_struct_impl.inc'
+ spec_extras[:files] << 'ext/pk11s_struct_def.inc'
+ spec_extras[:files] << 'ext/pk11s_const_def.inc'
+ spec_extras[:files] << 'ext/pk11s_struct.doc'
+ spec_extras[:files] << 'ext/pk11_struct_macros.h'
+ spec_extras[:files] << 'ext/pk11_const_macros.h'
+ spec_extras[:files] << 'ext/pk11_version.h'
+ spec_extras[:has_rdoc] = 'yard'
+end
+
+ENV['RUBY_CC_VERSION'] ||= '1.8.6:1.9.2'
+
+Rake::ExtensionTask.new('pkcs11_safenet_ext', hoe.spec) do |ext|
+ ext.ext_dir = 'ext'
+ ext.cross_compile = true # enable cross compilation (requires cross compile toolchain)
+ ext.cross_platform = ['i386-mswin32', 'i386-mingw32'] # forces the Windows platform instead of the default one
+
+ ext.config_options << "--with-safenet-sdk-dir=#{SAFENET_SDK_DIR.inspect}"
+ ext.config_options << "--with-ruby-pkcs11-include=#{RUBY_PKCS11_EXT_DIR}"
+end
+
+file 'ext/pk11_struct_macros.h' do |t|
+ cp File.join(RUBY_PKCS11_EXT_DIR, 'pk11_struct_macros.h'), t.name
+end
+file 'ext/pk11_const_macros.h' do |t|
+ cp File.join(RUBY_PKCS11_EXT_DIR, 'pk11_const_macros.h'), t.name
+end
+file 'ext/pk11_version.h' do |t|
+ cp File.join(RUBY_PKCS11_EXT_DIR, 'pk11_version.h'), t.name
+end
+
+file 'ext/extconf.rb' => ['ext/pk11s_struct_def.inc', 'ext/pk11s_const_def.inc', 'ext/pk11_struct_macros.h', 'ext/pk11_const_macros.h', 'ext/pk11_version.h']
+file 'ext/pk11s_struct_def.inc' => 'ext/generate_structs.rb' do
+ sh "#{Config::CONFIG['ruby_install_name']} ext/generate_structs.rb --def ext/pk11s_struct_def.inc --impl ext/pk11s_struct_impl.inc --doc ext/pk11s_struct.doc #{File.join(SAFENET_SDK_DIR, 'include/ctvdef.h').inspect}"
+end
+file 'ext/pk11s_struct_impl.inc' => 'ext/pk11s_struct_def.inc'
+file 'ext/pk11s_const_def.inc' => 'ext/generate_constants.rb' do
+ sh "#{Config::CONFIG['ruby_install_name']} ext/generate_constants.rb --const ext/pk11s_const_def.inc #{File.join(SAFENET_SDK_DIR, 'include/ctvdef.h').inspect}"
+end
+file 'ext/pk11s.c' => ['ext/pk11s_struct_def.inc', 'ext/pk11s_struct_impl.inc', 'ext/pk11s_const_def.inc']
+
+# vim: syntax=ruby
diff --git a/pkcs11-safenet/ext/extconf.rb b/pkcs11-safenet/ext/extconf.rb
new file mode 100644
index 0000000..d6bd0c5
--- /dev/null
+++ b/pkcs11-safenet/ext/extconf.rb
@@ -0,0 +1,16 @@
+require "mkmf"
+require "rubygems"
+
+inc, lib = dir_config('safenet-sdk', '/opt/ETcpsdk/include', '/opt/ETcpsdk/lib')
+puts "using Safenet-SDK include:#{inc} lib:#{lib}"
+
+# inc, lib = dir_config('ruby-pkcs11')
+# inc ||= Gem.required_location('pkcs11', '../ext')
+# puts "using ruby-pkcs11 include:#{inc} lib:#{lib}"
+# raise "path to ruby-pkcs11/ext could not be found (use --with-ruby-pkcs11-include=my_path)" unless inc
+# $INCFLAGS << " -I"+inc
+
+find_header('pk11_struct_macros.h')
+find_header('pk11_const_macros.h')
+
+create_makefile("pkcs11_safenet_ext");
diff --git a/pkcs11-safenet/ext/generate_constants.rb b/pkcs11-safenet/ext/generate_constants.rb
new file mode 100644
index 0000000..29fb1d0
--- /dev/null
+++ b/pkcs11-safenet/ext/generate_constants.rb
@@ -0,0 +1,42 @@
+#!/usr/bin/env ruby
+# Quick and dirty parser for PKCS#11 constants and
+# generator for Ruby wrapper classes.
+
+require 'rubygems'
+require Gem.required_location('pkcs11', '../ext/generate_constants.rb')
+
+module PKCS11
+module Safenet
+class ConstantParser < PKCS11::ConstantParser
+ ConstGroups = [
+ ConstTemplate.new(/#define\s+(CKM_[A-Z_0-9]+)\s+(.+)/, 'PKCS11_DEFINE_MECHANISM'),
+ ConstTemplate.new(/#define\s+(CKA_[A-Z_0-9]+)\s+(.+)/, 'PKCS11_DEFINE_ATTRIBUTE'),
+ ConstTemplate.new(/#define\s+(CKO_[A-Z_0-9]+)\s+(.+)/, 'PKCS11_DEFINE_OBJECT_CLASS'),
+ ConstTemplate.new(/#define\s+(CKR_[A-Z_0-9]+)\s+(.+)/, 'PKCS11_DEFINE_RETURN_VALUE'),
+ ]
+
+ IgnoreConstants = %w[CKR_CERTIFICATE_NOT_YET_ACTIVE CKR_CERTIFICATE_EXPIRED]
+
+ def start!
+ File.open(options.const, "w") do |fd_const|
+ options.files.each do |file_h|
+ c_src = IO.read(file_h)
+ ConstGroups.each do |const_group|
+ c_src.scan(const_group.regexp) do
+ const_name, const_value = $1, $2
+ next if IgnoreConstants.include?(const_name)
+
+ fd_const.puts "#{const_group.def}(#{const_name}); /* #{const_value} */"
+ end
+ end
+ end
+ end
+ end
+end
+end
+end
+
+
+if $0==__FILE__
+ PKCS11::Safenet::ConstantParser.run(ARGV)
+end
diff --git a/pkcs11-safenet/ext/generate_structs.rb b/pkcs11-safenet/ext/generate_structs.rb
new file mode 100644
index 0000000..29822b3
--- /dev/null
+++ b/pkcs11-safenet/ext/generate_structs.rb
@@ -0,0 +1,75 @@
+#!/usr/bin/env ruby
+# Quick and dirty parser for PKCS#11 structs and
+# generator for Ruby wrapper classes.
+
+require 'rubygems'
+require 'pkcs11'
+require '../ext/generate_structs.rb'
+
+module PKCS11
+module Safenet
+class StructParser < PKCS11::StructParser
+
+ SIZE_CONSTANTS = {
+ 'CK_MANUFACTURER_SIZE' => 32,
+ 'CK_SERIAL_NUMBER_SIZE' => 16,
+ 'CK_TIME_SIZE' => 16,
+ 'CK_LIB_DESC_SIZE' => 32,
+ 'CK_SLOT_DESCRIPTION_SIZE' => 64,
+ 'CK_SLOT_MANUFACTURER_SIZE' => 32,
+ 'CK_MAX_PIN_LEN' => 32,
+ 'CK_TOKEN_LABEL_SIZE' => 32,
+ 'CK_TOKEN_MANUFACTURER_SIZE' => 32,
+ 'CK_TOKEN_MODEL_SIZE' => 16,
+ 'CK_TOKEN_SERIAL_NUMBER_SIZE' => 16,
+ 'CK_TOKEN_TIME_SIZE' => 16,
+ 'CK_MAX_PBE_IV_SIZE' => 8,
+ 'CK_MAX_PAD_SIZE' => 16,
+ }
+
+ ULONG_TYPES = %w[CK_COUNT CK_SIZE CK_TIMESTAMP_FORMAT]
+ ULONG_PTR_TYPES = %w[CK_COUNT_PTR]
+
+ def struct_module
+ 'PKCS11::Safenet'
+ end
+
+ def array_attribute_names; %w[attributes mechanism certAttr hCert]; end
+
+ def parse_files(files)
+ structs = []
+ files.each do |file_h|
+ c_src = IO.read(file_h)
+ c_src.scan(/struct\s+([A-Z_0-9]+)\s*\{(.*?)\}/m) do |struct|
+ struct_text = $2
+ struct = PKCS11::StructParser::CStruct.new( $1, [] )
+
+ struct_text.scan(/^\s+([A-Z_0-9]+)([\*\s]+)([\w_]+)\s*(\[\s*(\w+)\s*\])?/) do |elem|
+ type, name = $1, $3
+ qual = SIZE_CONSTANTS[$5] || $5
+ ptr = $2.include?('*')
+ type = "CK_ULONG" if ULONG_TYPES.include?(type)
+ type = "CK_ULONG_PTR" if ULONG_PTR_TYPES.include?(type)
+ struct.attrs << Attribute.new(ptr ? type+"_PTR" : type, name, qual)
+ end
+ structs << struct
+ end
+ end
+ return structs
+ end
+
+ def start!
+ @structs = parse_files(options.files)
+ @structs_by_name = @structs.inject({}){|sum, v| sum[v.name]=v; sum }
+ @std_structs_by_name = PKCS11.constants.select{|c| PKCS11.const_get(c).respond_to?(:ancestors) && !(PKCS11.const_get(c).ancestors & [PKCS11::CStruct, PKCS11::CK_ATTRIBUTE]).empty? }.inject({}){|sum, v| sum[v]=true; sum }
+
+ write_files
+ end
+end
+end
+end
+
+
+if $0==__FILE__
+ PKCS11::Safenet::StructParser.run(ARGV)
+end
diff --git a/pkcs11-safenet/ext/pk11s.c b/pkcs11-safenet/ext/pk11s.c
new file mode 100644
index 0000000..a618c48
--- /dev/null
+++ b/pkcs11-safenet/ext/pk11s.c
@@ -0,0 +1,70 @@
+#include
+
+#if defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
+ #define _WINDOWS
+#endif
+
+#include
+#include "pk11_struct_macros.h"
+#include "pk11_const_macros.h"
+#include "pk11_version.h"
+
+///////////////////////////////////////
+
+#include "pk11s_struct_impl.inc"
+
+static VALUE mPKCS11;
+static VALUE mSafenet;
+static VALUE eSafenetError;
+static VALUE cSafenetCStruct;
+static VALUE cPkcs11CStruct;
+
+static VALUE vOBJECT_CLASSES;
+static VALUE vATTRIBUTES;
+static VALUE vMECHANISMS;
+static VALUE vRETURN_VALUES;
+
+#define MODULE_FOR_STRUCTS mSafenet
+#define MODULE_FOR_CONSTS mSafenet
+#define BASECLASS_FOR_ERRORS eSafenetError
+#define BASECLASS_FOR_STRUCTS cSafenetCStruct
+
+void
+Init_pkcs11_safenet_ext()
+{
+ VALUE eError;
+
+ mPKCS11 = rb_const_get(rb_cObject, rb_intern("PKCS11"));
+ mSafenet = rb_define_module_under(mPKCS11, "Safenet");
+
+ /* Library version */
+ rb_define_const( mSafenet, "VERSION", rb_str_new2(VERSION) );
+
+ eError = rb_const_get(mPKCS11, rb_intern("Error"));
+ /* Document-class: PKCS11::Safenet::Error
+ *
+ * Base class for all Safenet specific exceptions (CKR_*) */
+ eSafenetError = rb_define_class_under(mSafenet, "Error", eError);
+
+ cPkcs11CStruct = rb_const_get(mPKCS11, rb_intern("CStruct")); \
+ cSafenetCStruct = rb_define_class_under(mSafenet, "CStruct", cPkcs11CStruct);
+
+ #include "pk11s_struct_def.inc"
+
+ vOBJECT_CLASSES = rb_hash_new();
+ vATTRIBUTES = rb_hash_new();
+ vMECHANISMS = rb_hash_new();
+ vRETURN_VALUES = rb_hash_new();
+ rb_define_const(mSafenet, "OBJECT_CLASSES", vOBJECT_CLASSES);
+ rb_define_const(mSafenet, "ATTRIBUTES", vATTRIBUTES);
+ rb_define_const(mSafenet, "MECHANISMS", vMECHANISMS);
+ rb_define_const(mSafenet, "RETURN_VALUES", vRETURN_VALUES);
+
+ #include "pk11s_const_def.inc"
+
+ rb_obj_freeze(vOBJECT_CLASSES);
+ rb_obj_freeze(vATTRIBUTES);
+ rb_obj_freeze(vMECHANISMS);
+ rb_obj_freeze(vRETURN_VALUES);
+
+}
diff --git a/pkcs11-safenet/lib/pkcs11_safenet.rb b/pkcs11-safenet/lib/pkcs11_safenet.rb
new file mode 100644
index 0000000..3631976
--- /dev/null
+++ b/pkcs11-safenet/lib/pkcs11_safenet.rb
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+
+# Extend the search path for Windows binary gem, depending of the current ruby version
+major_minor = RUBY_VERSION[ /^(\d+\.\d+)/ ] or
+ raise "Oops, can't extract the major/minor version from #{RUBY_VERSION.dump}"
+$: << File.join(File.dirname(__FILE__), major_minor)
+
+require 'rubygems'
+require 'pkcs11'
+require 'pkcs11_safenet_ext'
+require 'pkcs11_safenet/extensions'
diff --git a/pkcs11-safenet/lib/pkcs11_safenet/extensions.rb b/pkcs11-safenet/lib/pkcs11_safenet/extensions.rb
new file mode 100644
index 0000000..feeff37
--- /dev/null
+++ b/pkcs11-safenet/lib/pkcs11_safenet/extensions.rb
@@ -0,0 +1,108 @@
+#!/usr/bin/env ruby
+
+module PKCS11
+module Safenet
+ # Derive CK_ATTRIBUTE to get converted attributes.
+ class CK_ATTRIBUTE < PKCS11::CK_ATTRIBUTE
+ ATTRIBUTES = {
+ CKA_EXPORT => :bool,
+ CKA_EXPORTABLE => :bool,
+ CKA_TRUSTED => :bool,
+ CKA_DELETABLE => :bool,
+ CKA_SIGN_LOCAL_CERT => :bool,
+ CKA_IMPORT => :bool,
+ CKA_USAGE_COUNT => :ulong,
+ CKA_KEY_SIZE => :ulong,
+ }
+
+ def value
+ case ATTRIBUTES[type]
+ when :bool
+ super != "\0"
+ when :ulong
+ super.unpack("L!")[0]
+ else
+ super
+ end
+ end
+ end
+
+ # A Safenet::Library instance holds a handle to the opened +cryptoki.dll+ or +cryptoki.so+ file.
+ #
+ # This class is derived from
+ # PKCS11::Library[http://pkcs11.rubyforge.org/pkcs11/PKCS11/Library.html] of pkcs11.gem.
+ class Library < PKCS11::Library
+ MechanismParameters = {
+ CKM_DES_DERIVE_CBC => CK_DES_CBC_PARAMS,
+ CKM_DES3_DERIVE_CBC => CK_DES3_CBC_PARAMS,
+ CKM_ECIES => CK_ECIES_PARAMS,
+ CKM_ENCODE_X_509 => CK_MECH_TYPE_AND_OBJECT,
+ CKM_PKCS12_PBE_EXPORT => CK_PKCS12_PBE_EXPORT_PARAMS,
+ CKM_PKCS12_PBE_IMPORT => CK_PKCS12_PBE_IMPORT_PARAMS,
+ CKM_PP_LOAD_SECRET => CK_PP_LOAD_SECRET_PARAMS,
+ CKM_REPLICATE_TOKEN_RSA_AES => CK_REPLICATE_TOKEN_PARAMS,
+ CKM_SECRET_RECOVER_WITH_ATTRIBUTES => CK_SECRET_SHARE_PARAMS,
+ CKM_SHA1_RSA_PKCS_TIMESTAMP => CK_TIMESTAMP_PARAMS,
+ }
+
+ # Path and file name of the loaded cryptoki library.
+ attr_reader :so_path
+
+ # Load and initialize a pkcs11 dynamic library with Safenet Protect Server extensions.
+ #
+ # Set +so_path+ to +:hsm+, +:sw+ or +:logger+ in order to autodetect the cryptoki-HSM or
+ # software emulation library file.
+ #
+ # @param [String, Symbol, nil] so_path Shortcut-Symbol or path to the *.so or *.dll file to load.
+ # @param [Hash, CK_C_INITIALIZE_ARGS] args A Hash or CK_C_INITIALIZE_ARGS instance with load params.
+ #
+ # See also PKCS11::Library#initialize[http://pkcs11.rubyforge.org/pkcs11/PKCS11/Library.html#initialize-instance_method] of pkcs11.gem
+ def initialize(so_path = nil, args = {})
+ if [:sw, :hsm].include?(so_path)
+ if RUBY_PLATFORM =~ /mswin|mingw/
+ libctsw_so = "cryptoki.dll"
+ libctsw_so_paths = [
+ File.join(ENV['ProgramFiles'], "SafeNet/ProtectToolkit C SDK/bin/#{so_path}"),
+ ]
+ else
+ libctsw_so = "libct#{so_path}.so"
+ libctsw_so_paths = [
+ "/opt/ETcpsdk/lib/linux-i386",
+ "/opt/PTK/lib",
+ ]
+ end
+
+ unless so_path=ENV['CRYPTOKI_SO']
+ paths = libctsw_so_paths.collect{|path| File.join(path, libctsw_so) }
+ so_path = paths.find{|path| File.exist?(path) }
+ end
+
+ raise "#{libctsw_so} not found - please install Safenet PTK-C or set ENV['CRYPTOKI_SO']" unless so_path
+ end
+
+ @so_path = so_path
+ super(so_path, args)
+ end
+
+ def vendor_const_get(name)
+ return Safenet.const_get(name) if Safenet.const_defined?(name)
+ super
+ end
+
+ def vendor_mechanism_parameter_struct(mech)
+ MechanismParameters[mech] || super
+ end
+
+ def vendor_raise_on_return_value(rv)
+ if ex=Safenet::RETURN_VALUES[rv]
+ raise(ex, rv.to_s)
+ end
+ super
+ end
+
+ def vendor_class_CK_ATTRIBUTE
+ Safenet::CK_ATTRIBUTE
+ end
+ end
+end
+end
diff --git a/pkcs11-safenet/test/helper.rb b/pkcs11-safenet/test/helper.rb
new file mode 100644
index 0000000..878c15a
--- /dev/null
+++ b/pkcs11-safenet/test/helper.rb
@@ -0,0 +1,14 @@
+def open_ctsw
+ PKCS11::Safenet::Library.new(:sw, :flags=>0)
+end
+
+def adjust_parity(data)
+ out = []
+ count_digit = "1"
+ data.each_byte{|b|
+ b &= 0xfe
+ b |= 1 if b.to_s(2).count(count_digit) % 2 == 0
+ out << b
+ }
+ return out.pack("C*")
+end
diff --git a/pkcs11-safenet/test/test_pkcs11_safenet.rb b/pkcs11-safenet/test/test_pkcs11_safenet.rb
new file mode 100644
index 0000000..7e8a599
--- /dev/null
+++ b/pkcs11-safenet/test/test_pkcs11_safenet.rb
@@ -0,0 +1,50 @@
+require "test/unit"
+require "pkcs11_safenet"
+require "test/helper"
+
+class TestPkcs11Safenet < Test::Unit::TestCase
+ include PKCS11
+
+ def test_CStruct
+ s = Safenet::CK_SECRET_SHARE_PARAMS.new
+ s.n, s.m = 2, 3
+
+ assert_match( /m=3/, s.inspect, 'There should be a n value in CK_SECRET_SHARE_PARAMS')
+ assert_equal ["n", "m"], s.members, 'CK_SECRET_SHARE_PARAMS should contain some attributes'
+ assert_equal [2, 3], s.values, 'values of CK_SECRET_SHARE_PARAMS'
+ assert_equal( {:n=>2, :m=>3}, s.to_hash, 'CK_SECRET_SHARE_PARAMS as hash' )
+ end
+
+ def test_CK_PKCS12_PBE_IMPORT_PARAMS
+ s = Safenet::CK_PKCS12_PBE_IMPORT_PARAMS.new
+ assert_equal [], s.certAttr
+ s1 = CK_ATTRIBUTE.new Safenet::CKA_EXPORT, true
+ s2 = CK_ATTRIBUTE.new Safenet::CKA_EXPORTABLE, false
+ s.certAttr = [s1, s2]
+ assert_equal [s1.to_hash, s2.to_hash], s.certAttr.map{|e| e.to_hash }
+ GC.start
+ assert_raise(ArgumentError){ s.certAttr = [s1, s2, nil] }
+ assert_equal [s1.to_hash, s2.to_hash], s.certAttr.map{|e| e.to_hash }
+
+ s.certAttr = []
+ assert_equal [], s.certAttr
+ end
+
+ def test_constants
+ assert_equal 0x80000990, Safenet::CKM_OS_UPGRADE, "CKM_OS_UPGRADE should be defined"
+ assert_equal 0x80000128, Safenet::CKA_EXPORT, "CKA_EXPORT should be defined"
+ assert_equal 0x80000129, Safenet::CKA_EXPORTABLE, "CKA_EXPORTABLE should be defined"
+ assert Safenet::CKR_ET_NOT_ODD_PARITY.ancestors.include?(PKCS11::Error), "CKR_ET_NOT_ODD_PARITY should be defined"
+ assert_equal 0x8000020c, Safenet::CKO_FM, "CKO_FM should be defined"
+ end
+
+ def test_loading
+ pk = PKCS11::Safenet::Library.new(:sw, :flags=>0)
+ so_path = pk.so_path
+ pk.close
+ assert !so_path.empty?, "Used path shouldn't be empty"
+
+ pk = PKCS11::Safenet::Library.new(so_path, :flags=>0)
+ pk.close
+ end
+end
diff --git a/pkcs11-safenet/test/test_pkcs11_safenet_crypt.rb b/pkcs11-safenet/test/test_pkcs11_safenet_crypt.rb
new file mode 100644
index 0000000..a959529
--- /dev/null
+++ b/pkcs11-safenet/test/test_pkcs11_safenet_crypt.rb
@@ -0,0 +1,102 @@
+require "test/unit"
+require "pkcs11_safenet"
+require "test/helper"
+
+class TestPkcs11SafenetCrypt < Test::Unit::TestCase
+ include PKCS11
+ attr_reader :slots
+ attr_reader :slot
+ attr_reader :session
+ attr_reader :secret_key
+
+ def setup
+ $pkcs11 ||= open_ctsw
+ @slots = pk.active_slots
+ @slot = slots.first
+
+ # Init SO-PIN if not already done.
+ if slot.token_info.flags & CKF_TOKEN_INITIALIZED == 0
+ slot.init_token('1234', 'test-token')
+ assert_match(/^test-token/, slot.token_info.label, "Token label should be set now")
+ end
+ assert_equal CKF_TOKEN_INITIALIZED, slot.token_info.flags & CKF_TOKEN_INITIALIZED, "Token should be initialized"
+
+ # Init USER-PIN if not already done.
+ if slot.token_info.flags & CKF_USER_PIN_INITIALIZED == 0
+ s = slot.open(CKF_SERIAL_SESSION | CKF_RW_SESSION)
+ assert_equal CKF_RW_SESSION, s.info.flags & CKF_RW_SESSION, "Session should be read/write"
+ assert_equal CKS_RW_PUBLIC_SESSION, s.info.state, "Session should be in logoff state"
+ s.login(:SO, '1234')
+ assert_equal CKS_RW_SO_FUNCTIONS, s.info.state, "Session should be in SO state"
+ s.init_pin('1234')
+ s.close
+ end
+ assert_equal CKF_USER_PIN_INITIALIZED, slot.token_info.flags & CKF_USER_PIN_INITIALIZED, "User PIN should be initialized"
+
+ @session = slot.open
+ assert_equal CKS_RO_PUBLIC_SESSION, session.info.state, "Session should be in logoff state"
+ session.login(:USER, ENV['CRYPTOKI_PIN'] || '1234')
+ assert_equal CKS_RO_USER_FUNCTIONS, session.info.state, "Session should be in USER state"
+
+ @secret_key = session.create_object(
+ :CLASS=>CKO_SECRET_KEY,
+ :KEY_TYPE=>CKK_DES2,
+ :ENCRYPT=>true, :WRAP=>true, :DECRYPT=>true, :UNWRAP=>true, :TOKEN=>false, :DERIVE=>true,
+ :USAGE_COUNT=>0, :EXPORTABLE=>true,
+ :VALUE=>adjust_parity("0123456789abcdef"),
+ :LABEL=>'test_secret_key')
+ end
+
+ def teardown
+ @secret_key.destroy
+ @session.logout
+ @session.close
+ end
+
+ def pk
+ $pkcs11
+ end
+
+ def test_bad_parity
+ assert_raise(Safenet::CKR_ET_NOT_ODD_PARITY) do
+ session.create_object(
+ :CLASS=>CKO_SECRET_KEY,
+ :KEY_TYPE=>CKK_DES2,
+ :VALUE=>"0123456789abcdef",
+ :LABEL=>'test_secret_key2')
+ end
+ end
+
+ def test_derive_des_cbc
+ pa = Safenet::CK_DES3_CBC_PARAMS.new
+ pa.data = "1"*16
+ pa.iv = "2"*8
+
+ new_key1 = session.derive_key( {Safenet::CKM_DES3_DERIVE_CBC => pa}, secret_key,
+ :CLASS=>CKO_SECRET_KEY, :KEY_TYPE=>CKK_DES2, :ENCRYPT=>true, :DECRYPT=>true, :SENSITIVE=>false )
+ assert_not_equal secret_key[:VALUE], new_key1[:VALUE], 'Derived key shouldn\'t have equal key value'
+
+ new_key2 = session.derive_key( {:DES3_DERIVE_CBC => {:data=>"1"*16, :iv=>"2"*16}}, secret_key,
+ :CLASS=>CKO_SECRET_KEY, :KEY_TYPE=>CKK_DES2, :ENCRYPT=>true, :DECRYPT=>true, :SENSITIVE=>false )
+ assert_equal new_key1[:VALUE], new_key2[:VALUE], 'Both derived key should be equal'
+
+ encrypted_key_value = session.encrypt( {:DES3_CBC => "2"*8}, secret_key, "1"*16)
+ encrypted_key_value = adjust_parity(encrypted_key_value)
+ assert_equal new_key1[:VALUE], encrypted_key_value, 'Encrypted data should equal derived key value'
+
+ assert_equal 3, secret_key[:USAGE_COUNT], 'The secret key should be used 3 times'
+ end
+
+
+ def test_attributes
+ assert_equal true, secret_key[:EXPORTABLE], 'CKA_EXTRACTABLE should be usable'
+ secret_key[:EXPORTABLE] = false
+ assert_equal false, secret_key[:EXPORTABLE], 'CKA_EXTRACTABLE should be usable'
+
+ assert_equal 0, secret_key[:USAGE_COUNT], 'CKA_USAGE_COUNT should be usable'
+ secret_key[:USAGE_COUNT] = 5
+ assert_equal 5, secret_key[:USAGE_COUNT], 'CKA_USAGE_COUNT should be usable'
+
+ assert_equal false, secret_key[:IMPORT], 'CKA_IMPORT should default to false'
+ end
+end