Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
First rough cut of PKCS8 exporter
  • Loading branch information
tadman committed Jan 16, 2012
1 parent f3b88e7 commit b68955b
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 101 deletions.
46 changes: 4 additions & 42 deletions .gitignore
@@ -1,48 +1,10 @@
# rcov generated
coverage

# rdoc generated
rdoc

# yard generated
doc
.yardoc

# bundler
.bundle

# jeweler generated
pkg

# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
#
# * Create a file at ~/.gitignore
# * Include files you want ignored
# * Run: git config --global core.excludesfile ~/.gitignore
#
# After doing this, these files will be ignored in all your git projects,
# saving you from having to 'pollute' every project you touch with them
#
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
#
# For MacOS:
#
#.DS_Store

# For TextMate
#*.tmproj
#tmtags

# For emacs:
#*~
#\#*
#.\#*

# For vim:
#*.swp

# For redcar:
#.redcar

# For rubinius:
#*.rbc
.DS_Store
*.bundle
Gemfile.lock
Makefile
8 changes: 0 additions & 8 deletions Gemfile
@@ -1,13 +1,5 @@
source "http://rubygems.org"
# Add dependencies required to use your gem here.
# Example:
# gem "activesupport", ">= 2.3.5"

# Add dependencies to develop your gem here.
# Include everything needed to run rake, tests, features, etc.
group :development do
gem "shoulda", ">= 0"
gem "bundler", "~> 1.0.0"
gem "jeweler", "~> 1.6.4"
gem "rcov", ">= 0"
end
41 changes: 8 additions & 33 deletions Rakefile
Expand Up @@ -9,45 +9,20 @@ rescue Bundler::BundlerError => e
$stderr.puts "Run `bundle install` to install missing gems"
exit e.status_code
end
require 'rake'

require 'rake'
require 'jeweler'

Jeweler::Tasks.new do |gem|
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
gem.name = "openssl-pkcs8"
gem.homepage = "http://github.com/tadman/openssl-pkcs8"
gem.name = "openssl_pkcs8"
gem.homepage = "http://github.com/twg/openssl_pkcs8"
gem.license = "MIT"
gem.summary = %Q{TODO: one-line summary of your gem}
gem.description = %Q{TODO: longer description of your gem}
gem.summary = %Q{OpenSSL RSA PKCS8 Extension}
gem.description = %Q{Adds PKCS8 key format support to OpenSSL::PKey::RSA}
gem.email = "github@tadman.ca"
gem.authors = ["Scott Tadman"]
gem.authors = [ "Scott Tadman" ]
# dependencies defined in Gemfile
end
Jeweler::RubygemsDotOrgTasks.new

require 'rake/testtask'
Rake::TestTask.new(:test) do |test|
test.libs << 'lib' << 'test'
test.pattern = 'test/**/test_*.rb'
test.verbose = true
end

require 'rcov/rcovtask'
Rcov::RcovTask.new do |test|
test.libs << 'test'
test.pattern = 'test/**/test_*.rb'
test.verbose = true
test.rcov_opts << '--exclude "gems/*"'
end

task :default => :test

require 'rake/rdoctask'
Rake::RDocTask.new do |rdoc|
version = File.exist?('VERSION') ? File.read('VERSION') : ""

rdoc.rdoc_dir = 'rdoc'
rdoc.title = "openssl-pkcs8 #{version}"
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end
Jeweler::RubygemsDotOrgTasks.new
1 change: 1 addition & 0 deletions VERSION
@@ -0,0 +1 @@
0.1.0
11 changes: 11 additions & 0 deletions ext/openssl_pkcs8/extconf.rb
@@ -0,0 +1,11 @@
require 'mkmf'

extension_name = 'openssl_pkcs8'

dir_config(extension_name)

have_header("openssl/ssl.h")
%w[crypto libeay32].any? {|lib| have_library(lib, "OpenSSL_add_all_digests")}
%w[ssl ssleay32].any? {|lib| have_library(lib, "SSL_library_init")}

create_makefile(extension_name)
191 changes: 191 additions & 0 deletions ext/openssl_pkcs8/openssl_pkcs8.c
@@ -0,0 +1,191 @@
#include "ruby.h"

#include <openssl/err.h>
#include <openssl/ossl_typ.h>
#include <openssl/engine.h>
#include <openssl/evp.h>
#include <openssl/asn1_mac.h>
#include <openssl/x509v3.h>
#include <openssl/ssl.h>
#include <openssl/pkcs12.h>
#include <openssl/pkcs7.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/conf.h>
#include <openssl/conf_api.h>

VALUE mOSSL;
VALUE mPKey;
VALUE cRSA;
VALUE eRSAError;
VALUE ePKeyError;
VALUE eOSSLError;
VALUE cCipher;

#define GetPKey(obj, pkey) do {\
Data_Get_Struct(obj, EVP_PKEY, pkey);\
if (!pkey) { \
rb_raise(rb_eRuntimeError, "PKEY wasn't initialized!");\
} \
} while (0)
#define GetPKeyRSA(obj, pkey) do { \
GetPKey(obj, pkey); \
if (EVP_PKEY_type(pkey->type) != EVP_PKEY_RSA) { /* PARANOIA? */ \
ossl_raise(rb_eRuntimeError, "THIS IS NOT A RSA!") ; \
} \
} while (0)
#define OSSL_PKEY_SET_PRIVATE(obj) rb_iv_set((obj), "private", Qtrue)
#define OSSL_PKEY_SET_PUBLIC(obj) rb_iv_set((obj), "private", Qfalse)
#define OSSL_PKEY_IS_PRIVATE(obj) (rb_iv_get((obj), "private") == Qtrue)
#define RSA_HAS_PRIVATE(rsa) ((rsa)->p && (rsa)->q)
#define GetCipher(obj, ctx) do { \
Data_Get_Struct(obj, EVP_CIPHER_CTX, ctx); \
if (!ctx) { \
ossl_raise(rb_eRuntimeError, "Cipher not inititalized!"); \
} \
} while (0)
#define SafeGetCipher(obj, ctx) do { \
OSSL_Check_Kind(obj, cCipher); \
GetCipher(obj, ctx); \
} while (0)

VALUE ossl_membio2str0(BIO *bio)
{
VALUE ret;
BUF_MEM *buf;

BIO_get_mem_ptr(bio, &buf);
ret = rb_str_new(buf->data, buf->length);

return ret;
}

VALUE ossl_protect_membio2str(BIO *bio, int *status)
{
return rb_protect((VALUE(*)_((VALUE)))ossl_membio2str0, (VALUE)bio, status);
}

VALUE ossl_membio2str(BIO *bio)
{
VALUE ret;
int status = 0;

ret = ossl_protect_membio2str(bio, &status);
BIO_free(bio);
if(status) rb_jump_tag(status);

return ret;
}

const EVP_CIPHER* GetCipherPtr(VALUE obj)
{
EVP_CIPHER_CTX* ctx;

SafeGetCipher(obj, ctx);

return EVP_CIPHER_CTX_cipher(ctx);
}

static VALUE openssl_pkcs8_pem_passwd_cb0(VALUE flag)
{
VALUE pass;

pass = rb_yield(flag);
SafeStringValue(pass);

return pass;
}

int openssl_pkcs8_pem_passwd_cb(char *buf, int max_len, int flag, void *pwd)
{
int len, status = 0;
VALUE rflag, pass;

if (pwd || !rb_block_given_p())
return PEM_def_callback(buf, max_len, flag, pwd);

while (1) {
/*
* when the flag is nonzero, this passphrase
* will be used to perform encryption; otherwise it will
* be used to perform decryption.
*/
rflag = flag ? Qtrue : Qfalse;
pass = rb_protect(openssl_pkcs8_pem_passwd_cb0, rflag, &status);
if (status) return -1; /* exception was raised. */
len = (int) RSTRING_LEN(pass);
if (len < 4) { /* 4 is OpenSSL hardcoded limit */
rb_warning("password must be longer than 4 bytes");
continue;
}
if (len > max_len) {
rb_warning("password must be shorter then %d bytes", max_len-1);
continue;
}
memcpy(buf, RSTRING_PTR(pass), len);
break;
}
return len;
}

static VALUE openssl_rsa_to_pem_pkcs8(int argc, VALUE *argv, VALUE self)
{
EVP_PKEY *pkey;
BIO *out;
const EVP_CIPHER *ciph = NULL;
char *passwd = NULL;
VALUE cipher, pass;

GetPKeyRSA(self, pkey);

rb_scan_args(argc, argv, "02", &cipher, &pass);

if (!NIL_P(cipher))
{
ciph = GetCipherPtr(cipher);
if (!NIL_P(pass))
{
passwd = StringValuePtr(pass);
}
}

if (!(out = BIO_new(BIO_s_mem())))
{
ossl_raise(eRSAError, NULL);
}

if (RSA_HAS_PRIVATE(pkey->pkey.rsa))
{
if (!PEM_write_bio_PKCS8PrivateKey(
out, pkey, ciph,
NULL, 0, openssl_pkcs8_pem_passwd_cb, passwd))
{
BIO_free(out);
ossl_raise(eRSAError, NULL);
}
}
else
{
if (!PEM_write_bio_PUBKEY(out, pkey))
{
BIO_free(out);
ossl_raise(eRSAError, NULL);
}
}

return ossl_membio2str(out);
}

void Init_openssl_pkcs8()
{
mOSSL = rb_const_get(rb_cObject, rb_intern("OpenSSL"));
mPKey = rb_const_get(mOSSL, rb_intern("PKey"));
cRSA = rb_const_get(mPKey, rb_intern("RSA"));
cCipher = rb_const_get(mOSSL, rb_intern("Cipher"));

eOSSLError = rb_const_get(mOSSL,rb_intern("OpenSSLError"));
ePKeyError = rb_const_get(mPKey, rb_intern("PKeyError"));
eRSAError = rb_const_get(mPKey, rb_intern("RSAError"));

rb_define_method(cRSA, "to_pem_pkcs8", openssl_rsa_to_pem_pkcs8, -1);
}
Binary file added ext/openssl_pkcs8/openssl_pkcs8.o
Binary file not shown.
Empty file removed lib/openssl-pkcs8.rb
Empty file.
3 changes: 3 additions & 0 deletions lib/openssl_pkcs8.rb
@@ -0,0 +1,3 @@
require 'openssl'

require 'openssl_pkcs8/openssl_pkcs8'
Binary file added openssl-pkcs8.o
Binary file not shown.
46 changes: 46 additions & 0 deletions openssl_pkcs8.gemspec
@@ -0,0 +1,46 @@
# Generated by jeweler
# DO NOT EDIT THIS FILE DIRECTLY
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
# -*- encoding: utf-8 -*-

Gem::Specification.new do |s|
s.name = "openssl_pkcs8"
s.version = "0.1.0"

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Scott Tadman"]
s.date = "2012-01-16"
s.description = "Adds PKCS8 key format support to OpenSSL::PKey::RSA"
s.email = "github@tadman.ca"
s.extensions = ["ext/openssl_pkcs8/extconf.rb"]
s.extra_rdoc_files = [
"LICENSE.txt",
"README.rdoc"
]
s.files = [
".document",
"Gemfile",
"LICENSE.txt",
"README.rdoc",
"Rakefile",
"test/helper.rb"
]
s.homepage = "http://github.com/twg/openssl_pkcs8"
s.licenses = ["MIT"]
s.require_paths = ["lib"]
s.rubygems_version = "1.8.11"
s.summary = "OpenSSL RSA PKCS8 Extension"

if s.respond_to? :specification_version then
s.specification_version = 3

if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
else
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
end
else
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
end
end

0 comments on commit b68955b

Please sign in to comment.