Skip to content

Commit

Permalink
pkey: add :format keyword argument to PKey::PKey#to_{der,pem}
Browse files Browse the repository at this point in the history
Currently they return XXXPrivateKey format (PKCS ruby#1 RSAPrivateKey for
RSA, OpenSSL's original format for DSA, anad SEC 1 ECPrivateKey for EC)
when the instance contains the private portion, or PUBKEY format
(SubjectPublicKeyInfo) otherwise. There is no option to force the
format, so users need to duplicate the key first and remove the private
key.

This resolves it by adding :format keyword argument to
PKey::PKey#to_{der,pem}. Users can pass either :PrivateKey or :PUBKEY.
  • Loading branch information
rhenium committed Jul 9, 2016
1 parent ee5d21b commit e94082e
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 137 deletions.
19 changes: 19 additions & 0 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,25 @@ DupPrivPKeyPtr(VALUE obj)
return pkey;
}

enum ossl_pkey_export_format
ossl_pkey_export_format(VALUE opts)
{
ID kw_ids[1], format_id;
VALUE kw_args[1];

kw_ids[0] = rb_intern("format");
rb_get_kwargs(opts, kw_ids, 1, 1, kw_args);
format_id = SYM2ID(kw_args[0]);

if (format_id == rb_intern("PUBKEY"))
return OSSL_PKEY_PUBKEY;
else if (format_id == rb_intern("PrivateKey"))
return OSSL_PKEY_PRIVATEKEY;
else
ossl_raise(rb_eArgError, "unknown format %"PRIsVALUE, kw_args[0]);
}


/*
* Private
*/
Expand Down
7 changes: 7 additions & 0 deletions ext/openssl/ossl_pkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ struct ossl_generate_cb_arg {
int ossl_generate_cb_2(int p, int n, BN_GENCB *cb);
void ossl_generate_cb_stop(void *ptr);

enum ossl_pkey_export_format {
OSSL_PKEY_PUBKEY, /* SubjectPublicKeyInfo */
OSSL_PKEY_PRIVATEKEY, /* traditional */
};

enum ossl_pkey_export_format ossl_pkey_export_format(VALUE);

VALUE ossl_pkey_new(EVP_PKEY *);
EVP_PKEY *GetPKeyPtr(VALUE);
EVP_PKEY *DupPKeyPtr(VALUE);
Expand Down
105 changes: 68 additions & 37 deletions ext/openssl/ossl_pkey_dsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -327,73 +327,104 @@ ossl_dsa_is_private(VALUE self)

/*
* call-seq:
* dsa.export([cipher, password]) -> aString
* dsa.to_pem([cipher, password]) -> aString
* dsa.to_s([cipher, password]) -> aString
* dsa.export([cipher, password], format: nil) -> aString
* dsa.to_pem([cipher, password], format: nil) -> aString
* dsa.to_s([cipher, password], format: nil) -> aString
*
* Encodes this DSA to its PEM encoding.
* Encodes this DSA to its PEM encoding. When +format+ is not specified, a
* default format will be used, depending on whether if the DSA contains private
* components or not.
*
* === Parameters
* * +cipher+ is an OpenSSL::Cipher.
* * +password+ is a string containing your password.
* * +format+ is a Symbol representing the output format. It must be either
* :PrivateKey or :PUBKEY.
*
* === Examples
* DSA.to_pem -> aString
* DSA.to_pem(cipher, 'mypassword') -> aString
*
* dsa = OpenSSL::PKey::DSA.new(1024) # generates a new key
* dsa.to_pem #=> "-----BEGIN DSA PRIVATE KEY-----\n..."
* dsa.to_pem(format: :PUBKEY) #=> "-----BEGIN PUBLIC KEY-----\n..."
*/
static VALUE
ossl_dsa_export(int argc, VALUE *argv, VALUE self)
{
DSA *dsa;
BIO *out;
const EVP_CIPHER *ciph = NULL;
VALUE cipher, pass, str;
VALUE cipher, pass, opts;
int ret;
enum ossl_pkey_export_format format;

rb_scan_args(argc, argv, "02:", &cipher, &pass, &opts);
GetDSA(self, dsa);
rb_scan_args(argc, argv, "02", &cipher, &pass);
if (!NIL_P(cipher)) {
ciph = GetCipherPtr(cipher);
pass = ossl_pem_passwd_value(pass);
}
if (!(out = BIO_new(BIO_s_mem()))) {
ossl_raise(eDSAError, NULL);
}
if (DSA_HAS_PRIVATE(dsa)) {
if (!PEM_write_bio_DSAPrivateKey(out, dsa, ciph, NULL, 0,
ossl_pem_passwd_cb, (void *)pass)){
BIO_free(out);
ossl_raise(eDSAError, NULL);
}
} else {
if (!PEM_write_bio_DSA_PUBKEY(out, dsa)) {
BIO_free(out);
ossl_raise(eDSAError, NULL);
if (!NIL_P(opts))
format = ossl_pkey_export_format(opts);
else
format = DSA_HAS_PRIVATE(dsa) ? OSSL_PKEY_PRIVATEKEY : OSSL_PKEY_PUBKEY;

if (format == OSSL_PKEY_PRIVATEKEY) {
if (!DSA_HAS_PRIVATE(dsa))
ossl_raise(eDSAError, "private key required");
if (!NIL_P(cipher)) {
ciph = GetCipherPtr(cipher);
pass = ossl_pem_passwd_value(pass);
}
}
str = ossl_membio2str(out);

return str;
if (!(out = BIO_new(BIO_s_mem())))
ossl_raise(eDSAError, "BIO_new");
switch (format) {
case OSSL_PKEY_PRIVATEKEY:
ret = PEM_write_bio_DSAPrivateKey(out, dsa, ciph, NULL, 0,
ossl_pem_passwd_cb, (void *)pass);
break;
case OSSL_PKEY_PUBKEY:
ret = PEM_write_bio_DSA_PUBKEY(out, dsa);
break;
default:
rb_notimplement();
}
if (!ret) {
BIO_free(out);
ossl_raise(eDSAError, NULL);
}
return ossl_membio2str(out);
}

/*
* call-seq:
* dsa.to_der -> aString
*
* Encodes this DSA to its DER encoding.
* call-seq:
* dsa.to_der(format: nil) -> aString
*
* Encodes the DSA into DER string. See #export for details of +format+
* parameter.
*/
static VALUE
ossl_dsa_to_der(VALUE self)
ossl_dsa_to_der(int argc, VALUE *argv, VALUE self)
{
DSA *dsa;
VALUE str;
VALUE opts, str;
enum ossl_pkey_export_format format;

rb_scan_args(argc, argv, "0:", &opts);
GetDSA(self, dsa);
if (DSA_HAS_PRIVATE(dsa))
ossl_i2d(str, dsa, i2d_DSAPrivateKey, eDSAError);
if (!NIL_P(opts))
format = ossl_pkey_export_format(opts);
else
format = DSA_HAS_PRIVATE(dsa) ? OSSL_PKEY_PRIVATEKEY : OSSL_PKEY_PUBKEY;

switch (format) {
case OSSL_PKEY_PRIVATEKEY:
if (!DSA_HAS_PRIVATE(dsa))
ossl_raise(eDSAError, "private key required");
ossl_i2d(str, dsa, i2d_DSAPrivateKey, eDSAError);
break;
case OSSL_PKEY_PUBKEY:
ossl_i2d(str, dsa, i2d_DSA_PUBKEY, eDSAError);
break;
default:
rb_notimplement();
}

return str;
}
Expand Down Expand Up @@ -628,7 +659,7 @@ Init_ossl_dsa(void)
rb_define_method(cDSA, "export", ossl_dsa_export, -1);
rb_define_alias(cDSA, "to_pem", "export");
rb_define_alias(cDSA, "to_s", "export");
rb_define_method(cDSA, "to_der", ossl_dsa_to_der, 0);
rb_define_method(cDSA, "to_der", ossl_dsa_to_der, -1);
rb_define_method(cDSA, "public_key", ossl_dsa_to_public_key, 0);
rb_define_method(cDSA, "syssign", ossl_dsa_sign, 1);
rb_define_method(cDSA, "sysverify", ossl_dsa_verify, 2);
Expand Down
148 changes: 77 additions & 71 deletions ext/openssl/ossl_pkey_ec.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ typedef struct {
int dont_free;
} ossl_ec_point;


#define EXPORT_PEM 0
#define EXPORT_DER 1

Expand Down Expand Up @@ -535,91 +534,98 @@ static VALUE ossl_ec_key_is_private(VALUE self)
return EC_KEY_get0_private_key(ec) ? Qtrue : Qfalse;
}

static VALUE ossl_ec_key_to_string(VALUE self, VALUE ciph, VALUE pass, int format)
/*
* call-seq:
* key.export([cipher, pass_phrase], format: nil) => String
* key.to_pem([cipher, pass_phrase], format: nil) => String
*
* Outputs the EC key in PEM encoding. If +cipher+ and +pass_phrase+ are given
* they will be used to encrypt the key. +cipher+ must be an OpenSSL::Cipher
* instance. Note that encryption will only be effective for a private key,
* public keys will always be encoded in plain text. +format+ is a Symbol that
* represents the output ASN.1 format. When not specified, the default format is
* used - SEC1 ECPrivateKey when there is a private key, SubjectPublicKeyInfo
* when not.
*/
static VALUE ossl_ec_key_export(int argc, VALUE *argv, VALUE self)
{
EC_KEY *ec;
BIO *out;
int i = -1;
int private = 0;
VALUE str;
const EVP_CIPHER *ciph = NULL;
VALUE cipher, pass, opts;
int ret;
enum ossl_pkey_export_format format;

rb_scan_args(argc, argv, "02:", &cipher, &pass, &opts);
Require_EC_KEY(self, ec);

if (EC_KEY_get0_public_key(ec) == NULL)
ossl_raise(eECError, "can't export - no public key set");

if (EC_KEY_check_key(ec) != 1)
ossl_raise(eECError, "can't export - EC_KEY_check_key failed");

if (EC_KEY_get0_private_key(ec))
private = 1;
if (!NIL_P(opts))
format = ossl_pkey_export_format(opts);
else
format = EC_KEY_get0_private_key(ec) ? OSSL_PKEY_PRIVATEKEY : OSSL_PKEY_PUBKEY;

if (format == OSSL_PKEY_PRIVATEKEY) {
if (!EC_KEY_get0_private_key(ec))
ossl_raise(eECError, "private key required");
if (!NIL_P(cipher)) {
ciph = GetCipherPtr(cipher);
pass = ossl_pem_passwd_value(pass);
}
}

if (!(out = BIO_new(BIO_s_mem())))
ossl_raise(eECError, "BIO_new(BIO_s_mem())");

switch(format) {
case EXPORT_PEM:
if (private) {
const EVP_CIPHER *cipher = NULL;
if (!NIL_P(ciph)) {
cipher = GetCipherPtr(ciph);
pass = ossl_pem_passwd_value(pass);
}
i = PEM_write_bio_ECPrivateKey(out, ec, cipher, NULL, 0, ossl_pem_passwd_cb, (void *)pass);
} else {
i = PEM_write_bio_EC_PUBKEY(out, ec);
}

break;
case EXPORT_DER:
if (private) {
i = i2d_ECPrivateKey_bio(out, ec);
} else {
i = i2d_EC_PUBKEY_bio(out, ec);
}

break;
default:
BIO_free(out);
ossl_raise(rb_eRuntimeError, "unknown format (internal error)");
ossl_raise(eECError, "BIO_new");
switch (format) {
case OSSL_PKEY_PRIVATEKEY:
ret = PEM_write_bio_ECPrivateKey(out, ec, ciph, NULL, 0,
ossl_pem_passwd_cb, (void *)pass);
break;
case OSSL_PKEY_PUBKEY:
ret = PEM_write_bio_EC_PUBKEY(out, ec);
break;
default:
rb_notimplement();
}

if (i != 1) {
BIO_free(out);
ossl_raise(eECError, "outlen=%d", i);
if (!ret) {
BIO_free(out);
ossl_raise(eECError, NULL);
}

str = ossl_membio2str(out);

return str;
return ossl_membio2str(out);
}

/*
* call-seq:
* key.export([cipher, pass_phrase]) => String
* key.to_pem([cipher, pass_phrase]) => String
* call-seq:
* key.to_der(format: nil) => String
*
* Outputs the EC key in PEM encoding. If +cipher+ and +pass_phrase+ are given
* they will be used to encrypt the key. +cipher+ must be an OpenSSL::Cipher
* instance. Note that encryption will only be effective for a private key,
* public keys will always be encoded in plain text.
* Encodes the EC into a DER string. +format+ is parsed in the same way as
* #export.
*/
static VALUE ossl_ec_key_export(int argc, VALUE *argv, VALUE self)
static VALUE ossl_ec_key_to_der(int argc, VALUE *argv, VALUE self)
{
VALUE cipher, passwd;
rb_scan_args(argc, argv, "02", &cipher, &passwd);
return ossl_ec_key_to_string(self, cipher, passwd, EXPORT_PEM);
}
EC_KEY *ec;
VALUE opts, str;
enum ossl_pkey_export_format format;

/*
* call-seq:
* key.to_der => String
*
* See the OpenSSL documentation for i2d_ECPrivateKey_bio()
*/
static VALUE ossl_ec_key_to_der(VALUE self)
{
return ossl_ec_key_to_string(self, Qnil, Qnil, EXPORT_DER);
rb_scan_args(argc, argv, "0:", &opts);
Require_EC_KEY(self, ec);
if (!NIL_P(opts))
format = ossl_pkey_export_format(opts);
else
format = EC_KEY_get0_private_key(ec) ? OSSL_PKEY_PRIVATEKEY : OSSL_PKEY_PUBKEY;

switch (format) {
case OSSL_PKEY_PRIVATEKEY:
if (!EC_KEY_get0_private_key(ec))
ossl_raise(eECError, "private key required");
ossl_i2d(str, ec, i2d_ECPrivateKey, eECError);
break;
case OSSL_PKEY_PUBKEY:
ossl_i2d(str, ec, i2d_EC_PUBKEY, eECError);
break;
default:
rb_notimplement();
}

return str;
}

/*
Expand Down Expand Up @@ -1755,7 +1761,7 @@ void Init_ossl_ec(void)

rb_define_method(cEC, "export", ossl_ec_key_export, -1);
rb_define_alias(cEC, "to_pem", "export");
rb_define_method(cEC, "to_der", ossl_ec_key_to_der, 0);
rb_define_method(cEC, "to_der", ossl_ec_key_to_der, -1);
rb_define_method(cEC, "to_text", ossl_ec_key_to_text, 0);


Expand Down
Loading

0 comments on commit e94082e

Please sign in to comment.