diff options
-rw-r--r-- | ChangeLog | 17 | ||||
-rw-r--r-- | ext/openssl/ossl_pkey.c | 53 | ||||
-rw-r--r-- | ext/openssl/ossl_pkey_dh.c | 19 | ||||
-rw-r--r-- | test/openssl/test_pkey_dsa.rb | 54 | ||||
-rw-r--r-- | test/openssl/test_pkey_ec.rb | 68 | ||||
-rw-r--r-- | test/openssl/test_pkey_rsa.rb | 47 | ||||
-rw-r--r-- | test/openssl/utils.rb | 8 |
7 files changed, 251 insertions, 15 deletions
@@ -1,3 +1,17 @@ +Mon Jun 13 05:17:29 2011 Martin Bosslet <[email protected]> + + * ext/openssl/ossl_pkey.c: added PKey.read module function that allows + reading arbitrary public/private keys from DER-/PEM-encoded File or + string instances. + * ext/openssl/ossl_pkey_dh.c: improved documentation. + * test/openssl/utils.rb: added EC test key. + * test/openssl/test_pkey_rsa.rb + test/openssl/test_pkey_dsa.rb: Test PKey.read. Reuse keys from + OpenSSL::TestUtils. + * test/openssl/test_pkey_ec.rb: Created test file for EC tests. + Test PKey.read. + [Ruby 1.9 - Feature #4424] [ruby-core:35330] + Mon Jun 13 04:42:24 2011 Koichi Sasada <[email protected]> * ext/objspace/objspace.c (total_i): fix to skip no ruby objects. @@ -85,8 +99,7 @@ Sun Jun 12 11:16:59 2011 Tanaka Akira <[email protected]> Sun Jun 12 09:32:13 2011 Nobuyoshi Nakada <[email protected]> - * atomic.h (ATOMIC_OR): _InterlockedOr is not available on migw. - + * atomic.h (ATOMIC_OR): _InterlockedOr is not available on migw.h * gc.c (rb_gc_set_params): VM_OBJSPACE is disabled on mignw. Sun Jun 12 01:07:09 2011 Tadayoshi Funaba <[email protected]> diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 69747ca44a..0bd9dda549 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -18,6 +18,9 @@ VALUE cPKey; VALUE ePKeyError; ID id_private_q; +#define reset_bio(b) (void)BIO_reset((b)); \ + (void)ERR_get_error(); + /* * callback for generating keys */ @@ -85,6 +88,54 @@ ossl_pkey_new_from_file(VALUE filename) return ossl_pkey_new(pkey); } +/* + * call-seq: + * OpenSSL::PKey.read(string [, pwd ] ) -> PKey + * OpenSSL::PKey.read(file [, pwd ]) -> PKey + * + * === Parameters + * * +string+ is a DER- or PEM-encoded string containing an arbitrary private + * or public key. + * * +file+ is an instance of +File+ containing a DER- or PEM-encoded + * arbitrary private or public key. + * * +pwd+ is an optional password in case +string+ or +file+ is an encrypted + * PEM resource. + */ +static VALUE +ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self) +{ + FILE *fp; + EVP_PKEY *pkey; + BIO *bio; + VALUE data, pass; + char *passwd = NULL; + + rb_scan_args(argc, argv, "11", &data, &pass); + + bio = ossl_obj2bio(data); + if (!(pkey = d2i_PrivateKey_bio(bio, NULL))) { + reset_bio(bio); + if (!NIL_P(pass)) { + passwd = StringValuePtr(pass); + } + if (!(pkey = PEM_read_bio_PrivateKey(bio, NULL, ossl_pem_passwd_cb, passwd))) { + reset_bio(bio); + if (!(pkey = d2i_PUBKEY_bio(bio, NULL))) { + reset_bio(bio); + if (!NIL_P(pass)) { + passwd = StringValuePtr(pass); + } + pkey = PEM_read_bio_PUBKEY(bio, NULL, ossl_pem_passwd_cb, passwd); + } + } + } + + BIO_free(bio); + if (!pkey) + ossl_raise(rb_eArgError, "Could not parse PKey"); + return ossl_pkey_new(pkey); +} + EVP_PKEY * GetPKeyPtr(VALUE obj) { @@ -330,6 +381,8 @@ Init_ossl_pkey() */ cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); + rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1); + rb_define_alloc_func(cPKey, ossl_pkey_alloc); rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0); diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 3f1ebda8d0..834261087a 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -338,15 +338,17 @@ ossl_dh_to_text(VALUE self) * call-seq: * dh.public_key -> aDH * - * Returns a new DH instance that carries just the public information. - * If the current instance has also private information, this will no + * Returns a new DH instance that carries just the public information, i.e. + * the prime +p+ and the generator +g+, but no public/private key yet. Such + * a pair may be generated using DH#generate_key!. + * If the current instance already contains private information, this will no * longer be present in the new instance. This feature is helpful for * publishing the public information without leaking any of the private * information. * * === Example * dh = OpenSSL::PKey::DH.new(2048) # has public and private information - * pub_key = dh.public_key # has only the public part available + * pub_key = dh.public_key # contains only prime and generator * pub_key_der = pub_key.to_der # it's safe to publish this */ static VALUE @@ -394,10 +396,15 @@ ossl_dh_check_params(VALUE self) /* * call-seq: - * dh.generate_key -> self + * dh.generate_key! -> self * - * Generates a private key unless one already exists. It also computes the - * public key for the generated private key. + * Generates a private and public key unless one already exists. + * + * === Example + * dh = OpenSSL::PKey::DH.new(2048) + * public_key = dh.public_key #contains no private/public key yet + * public_key.generate_key! + * puts public_key.private? # => true */ static VALUE ossl_dh_generate_key(VALUE self) diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb index 725937464d..ba3bfb5b4a 100644 --- a/test/openssl/test_pkey_dsa.rb +++ b/test/openssl/test_pkey_dsa.rb @@ -23,7 +23,7 @@ class OpenSSL::TestPKeyDSA < Test::Unit::TestCase end def test_sys_sign_verify - key = OpenSSL::PKey::DSA.new(256) + key = OpenSSL::TestUtils::TEST_KEY_DSA256 data = 'Sign me!' digest = OpenSSL::Digest::SHA1.digest(data) sig = key.syssign(digest) @@ -45,7 +45,7 @@ if (OpenSSL::OPENSSL_VERSION_NUMBER > 0x10000000) end def test_digest_state_irrelevant_verify - key = OpenSSL::PKey::DSA.new(256) + key = OpenSSL::TestUtils::TEST_KEY_DSA256 digest1 = OpenSSL::Digest::DSS1.new digest2 = OpenSSL::Digest::DSS1.new data = 'Sign me!' @@ -128,7 +128,7 @@ YNMbNw== end def test_export_format_is_DSA_PUBKEY_pem - key = OpenSSL::PKey::DSA.new(256) + key = OpenSSL::TestUtils::TEST_KEY_DSA256 pem = key.public_key.to_pem pem.gsub!(/^-+(\w|\s)+-+$/, "") # eliminate --------BEGIN...------- asn1 = OpenSSL::ASN1.decode(Base64.decode64(pem)) @@ -156,10 +156,56 @@ YNMbNw== assert_equal(key.pub_key, pub_key.value) end + def test_read_private_key_der + key = OpenSSL::TestUtils::TEST_KEY_DSA256 + der = key.to_der + key2 = OpenSSL::PKey.read(der) + assert(key2.private?) + assert_equal(der, key2.to_der) + end + + def test_read_private_key_pem + key = OpenSSL::TestUtils::TEST_KEY_DSA256 + pem = key.to_pem + key2 = OpenSSL::PKey.read(pem) + assert(key2.private?) + assert_equal(pem, key2.to_pem) + end + + def test_read_public_key_der + key = OpenSSL::TestUtils::TEST_KEY_DSA256.public_key + der = key.to_der + key2 = OpenSSL::PKey.read(der) + assert(!key2.private?) + assert_equal(der, key2.to_der) + end + + def test_read_public_key_pem + key = OpenSSL::TestUtils::TEST_KEY_DSA256.public_key + pem = key.to_pem + key2 = OpenSSL::PKey.read(pem) + assert(!key2.private?) + assert_equal(pem, key2.to_pem) + end + + def test_read_private_key_pem_pw + key = OpenSSL::TestUtils::TEST_KEY_DSA256 + pem = key.to_pem(OpenSSL::Cipher.new('AES-128-CBC'), 'secret') + #callback form for password + key2 = OpenSSL::PKey.read(pem) do + 'secret' + end + assert(key2.private?) + # pass password directly + key2 = OpenSSL::PKey.read(pem, 'secret') + assert(key2.private?) + #omit pem equality check, will be different due to cipher iv + end + private def check_sign_verify(digest) - key = OpenSSL::PKey::DSA.new(256) + key = OpenSSL::TestUtils::TEST_KEY_DSA256 data = 'Sign me!' sig = key.sign(digest, data) assert(key.verify(digest, sig, data)) diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb new file mode 100644 index 0000000000..b80e1e56ec --- /dev/null +++ b/test/openssl/test_pkey_ec.rb @@ -0,0 +1,68 @@ +require_relative 'utils' + +if defined?(OpenSSL) + +class OpenSSL::TestPKeyEC < Test::Unit::TestCase + def test_new + group = OpenSSL::PKey::EC::Group.new('prime256v1') + ec = OpenSSL::PKey::EC.new(group) + ec.generate_key + assert(ec.private_key?) + assert(ec.public_key?) + end + + def test_read_private_key_der + ec = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + der = ec.to_der + ec2 = OpenSSL::PKey.read(der) + assert(ec2.private_key?) + assert_equal(der, ec2.to_der) + end + + def test_read_private_key_pem + ec = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + pem = ec.to_pem + ec2 = OpenSSL::PKey.read(pem) + assert(ec2.private_key?) + assert_equal(pem, ec2.to_pem) + end + + def test_read_public_key_der + ec = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + group = OpenSSL::PKey::EC::Group.new('prime256v1') + ec2 = OpenSSL::PKey::EC.new(group) + ec2.public_key = ec.public_key + der = ec2.to_der + ec3 = OpenSSL::PKey.read(der) + assert(!ec3.private_key?) + assert_equal(der, ec3.to_der) + end + + def test_read_public_key_pem + ec = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + group = OpenSSL::PKey::EC::Group.new('prime256v1') + ec2 = OpenSSL::PKey::EC.new(group) + ec2.public_key = ec.public_key + pem = ec2.to_pem + ec3 = OpenSSL::PKey.read(pem) + assert(!ec3.private_key?) + assert_equal(pem, ec3.to_pem) + end + + def test_read_private_key_pem_pw + ec = OpenSSL::TestUtils::TEST_KEY_EC_P256V1 + pem = ec.to_pem(OpenSSL::Cipher.new('AES-128-CBC'), 'secret') + #callback form for password + ec2 = OpenSSL::PKey.read(pem) do + 'secret' + end + assert(ec2.private_key?) + # pass password directly + ec2 = OpenSSL::PKey.read(pem, 'secret') + assert(ec2.private_key?) + #omit pem equality check, will be different due to cipher iv + end + +end + +end diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb index 8592f0efa0..ec49c0fb61 100644 --- a/test/openssl/test_pkey_rsa.rb +++ b/test/openssl/test_pkey_rsa.rb @@ -49,7 +49,7 @@ class OpenSSL::TestPKeyRSA < Test::Unit::TestCase end def test_sign_verify - key = OpenSSL::PKey::RSA.new(512) + key = OpenSSL::TestUtils::TEST_KEY_RSA1024 digest = OpenSSL::Digest::SHA1.new data = 'Sign me!' sig = key.sign(digest, data) @@ -57,7 +57,7 @@ class OpenSSL::TestPKeyRSA < Test::Unit::TestCase end def test_digest_state_irrelevant_sign - key = OpenSSL::PKey::RSA.new(512) + key = OpenSSL::TestUtils::TEST_KEY_RSA1024 digest1 = OpenSSL::Digest::SHA1.new digest2 = OpenSSL::Digest::SHA1.new data = 'Sign me!' @@ -68,7 +68,7 @@ class OpenSSL::TestPKeyRSA < Test::Unit::TestCase end def test_digest_state_irrelevant_verify - key = OpenSSL::PKey::RSA.new(512) + key = OpenSSL::TestUtils::TEST_KEY_RSA1024 digest1 = OpenSSL::Digest::SHA1.new digest2 = OpenSSL::Digest::SHA1.new data = 'Sign me!' @@ -164,6 +164,47 @@ AwEAAQ== check_PUBKEY(asn1, key) end + def test_read_private_key_der + der = OpenSSL::TestUtils::TEST_KEY_RSA1024.to_der + key = OpenSSL::PKey.read(der) + assert(key.private?) + assert_equal(der, key.to_der) + end + + def test_read_private_key_pem + pem = OpenSSL::TestUtils::TEST_KEY_RSA1024.to_pem + key = OpenSSL::PKey.read(pem) + assert(key.private?) + assert_equal(pem, key.to_pem) + end + + def test_read_public_key_der + der = OpenSSL::TestUtils::TEST_KEY_RSA1024.public_key.to_der + key = OpenSSL::PKey.read(der) + assert(!key.private?) + assert_equal(der, key.to_der) + end + + def test_read_public_key_pem + pem = OpenSSL::TestUtils::TEST_KEY_RSA1024.public_key.to_pem + key = OpenSSL::PKey.read(pem) + assert(!key.private?) + assert_equal(pem, key.to_pem) + end + + def test_read_private_key_pem_pw + pem = OpenSSL::TestUtils::TEST_KEY_RSA1024.to_pem(OpenSSL::Cipher.new('AES-128-CBC'), 'secret') + #callback form for password + key = OpenSSL::PKey.read(pem) do + 'secret' + end + assert(key.private?) + # pass password directly + key = OpenSSL::PKey.read(pem, 'secret') + assert(key.private?) + #omit pem equality check, will be different due to cipher iv + end + private def check_PUBKEY(asn1, key) diff --git a/test/openssl/utils.rb b/test/openssl/utils.rb index 0b2115bbf3..bb51f57c72 100644 --- a/test/openssl/utils.rb +++ b/test/openssl/utils.rb @@ -80,6 +80,14 @@ Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S -----END DSA PRIVATE KEY----- _end_of_pem_ + TEST_KEY_EC_P256V1 = OpenSSL::PKey::EC.new <<-_end_of_pem_ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIID49FDqcf1O1eO8saTgG70UbXQw9Fqwseliit2aWhH1oAoGCCqGSM49 +AwEHoUQDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7lSZ/ArK41eGy5wAzU/0G51Xtt +CeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg== +-----END EC PRIVATE KEY----- + _end_of_pem_ + module_function def issue_cert(dn, key, serial, not_before, not_after, extensions, |