Skip to content

Commit fe73b31

Browse files
committed
pkey: add :format keyword argument to PKey::PKey#to_{der,pem}
Currently they return XXXPrivateKey format (PKCS ruby#1 RSAPrivateKey for RSA, OpenSSL format for DSA, anad SEC 1 ECPrivateKey for EC) when the instance contains the private key, or SubjectPublicKeyInfo) otherwise. There is no option to force the format, so users need to duplicate the key first and remove the private key if they want SubjectPublicKeyInfo DER from a key. This resolves it by adding :format keyword argument to PKey::PKey#to_{der,pem}. Users can pass either :*PrivateKey or :SubjectPublicKeyInfo.
1 parent dd62361 commit fe73b31

File tree

4 files changed

+112
-14
lines changed

4 files changed

+112
-14
lines changed

ext/openssl/ossl_pkey.c

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -190,35 +190,83 @@ DupPrivPKeyPtr(VALUE obj)
190190
return pkey;
191191
}
192192

193+
static enum ossl_pkey_export_format
194+
export_format(VALUE obj, int type, VALUE opts)
195+
{
196+
ID kwids[1], id;
197+
VALUE kwargs[1];
198+
199+
kwids[0] = rb_intern("format");
200+
rb_get_kwargs(opts, kwids, 0, 1, kwargs);
201+
if (kwargs[0] == Qundef) {
202+
if (RTEST(rb_funcall(obj, id_private_q, 0))) /* XXX */
203+
return OSSL_PKEY_PRIVKEY;
204+
else
205+
return OSSL_PKEY_PUBKEY;
206+
}
207+
208+
id = SYM2ID(kwargs[0]);
209+
if (id == rb_intern("SubjectPublicKeyInfo")) {
210+
return OSSL_PKEY_PUBKEY;
211+
}
212+
else {
213+
if (!RTEST(rb_funcall(obj, id_private_q, 0))) /* XXX */
214+
ossl_raise(ePKeyError, "private key required");
215+
if (type == EVP_PKEY_RSA && id == rb_intern("RSAPrivateKey") ||
216+
type == EVP_PKEY_DSA && id == rb_intern("DSAPrivateKey") ||
217+
#if !defined(OPENSSL_NO_EC)
218+
type == EVP_PKEY_EC && id == rb_intern("ECPrivateKey") ||
219+
#endif
220+
0) {
221+
return OSSL_PKEY_PRIVKEY;
222+
}
223+
else {
224+
ossl_raise(rb_eArgError, "unsupported format %"PRIsVALUE, kwargs[0]);
225+
}
226+
}
227+
}
228+
193229
/*
194230
* call-seq:
195-
* key.export([cipher, pass_phrase]) -> String
196-
* key.to_pem([cipher, pass_phrase]) -> String
197-
* key.to_s([cipher, pass_phrase]) -> String
231+
* key.export([cipher, pass_phrase], format: nil) -> String
232+
* key.to_pem([cipher, pass_phrase], format: nil) -> String
233+
* key.to_s([cipher, pass_phrase], format: nil) -> String
198234
*
199235
* Encodes the PKey into a PEM string. If +cipher+ and +pass_phrase+ are given,
200236
* they will be used to encrypt the key. +cipher+ must be an OpenSSL::Cipher
201237
* instance. Note that encryption will only be effective for a private key,
202-
* public keys will always be encoded in unencrypted format.
238+
* public keys will always be encoded in unencrypted format. The output format
239+
* can be specified with +format+. It must be one of these:
240+
*
241+
* :SubjectPublicKeyInfo:: X.509 SubjectPublicKeyInfo format
242+
* :RSAPrivateKey:: PKCS #1 RSAPrivateKey format
243+
* :DSAPrivateKey:: OpenSSL DSAPrivateKey format
244+
* :ECPrivateKey:: SEC 1 ECPrivateKey format
245+
*
246+
* When +format+ is not given, the default (:SubjectPublicKeyInfo for public
247+
* keys, :*PrivateKey for private keys) is used.
203248
*
204249
* === Examples
205250
* dsa = OpenSSL::PKey::DSA.new(1024) # generates a new key
206-
* dsa.to_pem #=> "-----BEGIN DSA PRIVATE KEY-----\n..."
251+
* dsa.to_pem
252+
* #=> "-----BEGIN DSA PRIVATE KEY-----\n..."
253+
* dsa.to_pem(format: :SubjectPublicKeyInfo)
254+
* #=> "-----BEGIN PUBLIC KEY-----\n..."
207255
*/
208256
static VALUE
209257
ossl_pkey_export(int argc, VALUE *argv, VALUE self)
210258
{
211259
EVP_PKEY *pkey;
212260
BIO *out;
213261
const EVP_CIPHER *ciph = NULL;
214-
VALUE cipher, pass;
262+
VALUE cipher, pass, opts;
215263
int ret, type;
216264
enum ossl_pkey_export_format format;
217265

218266
GetPKey(self, pkey);
219267
type = EVP_PKEY_base_id(pkey);
220-
rb_scan_args(argc, argv, "02", &cipher, &pass);
221-
format = RTEST(rb_funcall(self, id_private_q, 0)) ? OSSL_PKEY_PRIVKEY : OSSL_PKEY_PUBKEY;
268+
rb_scan_args(argc, argv, "02:", &cipher, &pass, &opts);
269+
format = export_format(self, type, opts);
222270
if (!NIL_P(cipher)) {
223271
ciph = GetCipherPtr(cipher);
224272
pass = ossl_pem_passwd_value(pass);
@@ -265,21 +313,23 @@ ossl_pkey_export(int argc, VALUE *argv, VALUE self)
265313

266314
/*
267315
* call-seq:
268-
* key.to_der -> String
316+
* key.to_der(format: nil) -> String
269317
*
270-
* Encodes the PKey into a DER string.
318+
* Encodes the PKey into a DER string. +format+ is handled in the same way as
319+
* #export.
271320
*/
272321
static VALUE
273-
ossl_pkey_to_der(VALUE self)
322+
ossl_pkey_to_der(int argc, VALUE *argv, VALUE self)
274323
{
275324
EVP_PKEY *pkey;
276-
VALUE str;
325+
VALUE opts, str;
277326
int type;
278327
enum ossl_pkey_export_format format;
279328

280329
GetPKey(self, pkey);
281330
type = EVP_PKEY_base_id(pkey);
282-
format = RTEST(rb_funcall(self, id_private_q, 0)) ? OSSL_PKEY_PRIVKEY : OSSL_PKEY_PUBKEY;
331+
rb_scan_args(argc, argv, "0:", &opts);
332+
format = export_format(self, type, opts);
283333

284334
switch (format) {
285335
case OSSL_PKEY_PUBKEY:
@@ -537,7 +587,7 @@ Init_ossl_pkey(void)
537587
rb_define_method(cPKey, "export", ossl_pkey_export, -1);
538588
rb_define_alias(cPKey, "to_pem", "export");
539589
rb_define_alias(cPKey, "to_s", "export");
540-
rb_define_method(cPKey, "to_der", ossl_pkey_to_der, 0);
590+
rb_define_method(cPKey, "to_der", ossl_pkey_to_der, -1);
541591

542592
id_private_q = rb_intern("private?");
543593

test/test_pkey_dsa.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,20 +134,31 @@ def test_to_der_DSAPrivateKey
134134
# defaults to DSAPrivateKey format when priv_key is present
135135
der = DSA512.to_der
136136
check_DSAPrivateKey(der, DSA512)
137+
138+
assert_raise(OpenSSL::PKey::PKeyError) {
139+
dup_public(DSA512).to_der(format: :DSAPrivateKey)
140+
}
137141
end
138142

139143
def test_to_der_DSA_PUBKEY
140144
dsa = dup_public(DSA512)
141145
# defaults to PUBKEY format when priv_key is absent
142146
der = dsa.to_der
143147
check_PUBKEY(der, DSA512)
148+
149+
der = DSA512.to_der(format: :SubjectPublicKeyInfo)
150+
check_PUBKEY(der, DSA512)
144151
end
145152

146153
def test_export_DSAPrivateKey
147154
pem = DSA512.to_pem
148155
header, *body, _ = pem.lines
149156
assert_equal("-----BEGIN DSA PRIVATE KEY-----\n", header)
150157
check_DSAPrivateKey(body.join.unpack("m")[0], DSA512)
158+
159+
assert_raise(OpenSSL::PKey::PKeyError) {
160+
dup_public(DSA512).to_pem(format: :DSAPrivateKey)
161+
}
151162
end
152163

153164
def test_export_DSA_PUBKEY
@@ -156,6 +167,11 @@ def test_export_DSA_PUBKEY
156167
header, *body, _ = pem.lines
157168
assert_equal("-----BEGIN PUBLIC KEY-----\n", header)
158169
check_PUBKEY(body.join.unpack("m")[0], DSA512)
170+
171+
pem = DSA512.to_pem(format: :SubjectPublicKeyInfo)
172+
header, *body, _ = pem.lines
173+
assert_equal("-----BEGIN PUBLIC KEY-----\n", header)
174+
check_PUBKEY(body.join.unpack("m")[0], DSA512)
159175
end
160176

161177
def test_dup

test/test_pkey_ec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,19 +235,30 @@ def test_read_EC_PUBKEY_pem
235235
def test_to_der_ECPrivateKey
236236
der = P256.to_der
237237
check_ECPrivateKey(der, P256)
238+
239+
assert_raise(OpenSSL::PKey::PKeyError) {
240+
dup_public(P256).to_der(format: :ECPrivateKey)
241+
}
238242
end
239243

240244
def test_to_der_EC_PUBKEY
241245
ec = dup_public(P256)
242246
der = ec.to_der
243247
check_PUBKEY(der, P256)
248+
249+
der = P256.to_der(format: :SubjectPublicKeyInfo)
250+
check_PUBKEY(der, P256)
244251
end
245252

246253
def test_export_ECPrivateKey
247254
pem = P256.to_pem
248255
header, *body, _ = pem.lines
249256
assert_equal("-----BEGIN EC PRIVATE KEY-----\n", header)
250257
check_ECPrivateKey(body.join.unpack("m")[0], P256)
258+
259+
assert_raise(OpenSSL::PKey::PKeyError) {
260+
dup_public(P256).to_pem(format: :ECPrivateKey)
261+
}
251262
end
252263

253264
def test_export_EC_PUBKEY
@@ -256,6 +267,11 @@ def test_export_EC_PUBKEY
256267
header, *body, _ = pem.lines
257268
assert_equal("-----BEGIN PUBLIC KEY-----\n", header)
258269
check_PUBKEY(body.join.unpack("m")[0], P256)
270+
271+
pem = P256.to_pem(format: :SubjectPublicKeyInfo)
272+
header, *body, _ = pem.lines
273+
assert_equal("-----BEGIN PUBLIC KEY-----\n", header)
274+
check_PUBKEY(body.join.unpack("m")[0], P256)
259275
end
260276

261277
def test_ec_point_mul

test/test_pkey_rsa.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,20 +222,31 @@ def test_to_der_RSAPrivateKey
222222
# defaults to RSAPrivateKey format when private components are present
223223
der = RSA1024.to_der
224224
check_RSAPrivateKey(der, RSA1024)
225+
226+
assert_raise(OpenSSL::PKey::PKeyError) {
227+
dup_public(RSA1024).to_der(format: :RSAPrivateKey)
228+
}
225229
end
226230

227231
def test_to_der_RSA_PUBKEY
228232
rsa = dup_public(RSA1024)
229233
# defaults to PUBKEY format when private components are absent
230234
der = rsa.to_der
231235
check_PUBKEY(der, RSA1024)
236+
237+
der = RSA1024.to_der(format: :SubjectPublicKeyInfo)
238+
check_PUBKEY(der, RSA1024)
232239
end
233240

234241
def test_export_RSAPrivateKey
235242
pem = RSA1024.to_pem
236243
header, *body, _ = pem.lines
237244
assert_equal("-----BEGIN RSA PRIVATE KEY-----\n", header)
238245
check_RSAPrivateKey(body.join.unpack("m")[0], RSA1024)
246+
247+
assert_raise(OpenSSL::PKey::PKeyError) {
248+
dup_public(RSA1024).to_pem(format: :RSAPrivateKey)
249+
}
239250
end
240251

241252
def test_export_RSA_PUBKEY
@@ -244,6 +255,11 @@ def test_export_RSA_PUBKEY
244255
header, *body, _ = pem.lines
245256
assert_equal("-----BEGIN PUBLIC KEY-----\n", header)
246257
check_PUBKEY(body.join.unpack("m")[0], RSA1024)
258+
259+
pem = RSA1024.to_pem(format: :SubjectPublicKeyInfo)
260+
header, *body, _ = pem.lines
261+
assert_equal("-----BEGIN PUBLIC KEY-----\n", header)
262+
check_PUBKEY(body.join.unpack("m")[0], RSA1024)
247263
end
248264

249265
def test_dup

0 commit comments

Comments
 (0)