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