Skip to content

Commit 60ce7ea

Browse files
committed
pkey/ec: add support for octet string encoding of EC point
Add a new method named PKey::EC#to_octet_string that returns the octet string representation of the curve point. PKey::EC::Point#to_bn, which have already existed and is similar except that an instance of OpenSSL::BN is returned, is rewritten in Ruby. PKey::EC::Point#initialize now takes String as the second argument in the PKey::EC::Point.new(group, encoded_point) form. Also, update the tests to use #to_octet_string instead of #to_bn for better readability.
1 parent 717f4b9 commit 60ce7ea

File tree

3 files changed

+108
-58
lines changed

3 files changed

+108
-58
lines changed

ext/openssl/ossl_pkey_ec.c

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,10 +1323,14 @@ static VALUE ossl_ec_point_initialize_copy(VALUE, VALUE);
13231323
/*
13241324
* call-seq:
13251325
* OpenSSL::PKey::EC::Point.new(point)
1326-
* OpenSSL::PKey::EC::Point.new(group)
1327-
* OpenSSL::PKey::EC::Point.new(group, bn)
1326+
* OpenSSL::PKey::EC::Point.new(group [, encoded_point])
13281327
*
1329-
* See the OpenSSL documentation for EC_POINT_*
1328+
* Creates a new instance of OpenSSL::PKey::EC::Point. If the only argument is
1329+
* an instance of EC::Point, a copy is returned. Otherwise, creates a point
1330+
* that belongs to _group_.
1331+
*
1332+
* _encoded_point_ is the octet string representation of the point. This
1333+
* must be either a String or an OpenSSL::BN.
13301334
*/
13311335
static VALUE ossl_ec_point_initialize(int argc, VALUE *argv, VALUE self)
13321336
{
@@ -1358,16 +1362,17 @@ static VALUE ossl_ec_point_initialize(int argc, VALUE *argv, VALUE self)
13581362
ossl_raise(eEC_POINT, "EC_POINT_bn2point");
13591363
}
13601364
else {
1361-
BIO *in = ossl_obj2bio(&arg2);
1362-
1363-
/* BUG: finish me */
1364-
1365-
BIO_free(in);
1366-
1367-
if (point == NULL) {
1368-
ossl_raise(eEC_POINT, "unknown type for 2nd arg");
1369-
}
1370-
}
1365+
StringValue(arg2);
1366+
point = EC_POINT_new(group);
1367+
if (!point)
1368+
ossl_raise(eEC_POINT, "EC_POINT_new");
1369+
if (!EC_POINT_oct2point(group, point,
1370+
(unsigned char *)RSTRING_PTR(arg2),
1371+
RSTRING_LEN(arg2), ossl_bn_ctx)) {
1372+
EC_POINT_free(point);
1373+
ossl_raise(eEC_POINT, "EC_POINT_oct2point");
1374+
}
1375+
}
13711376
}
13721377

13731378
RTYPEDDATA_DATA(self) = point;
@@ -1523,38 +1528,38 @@ static VALUE ossl_ec_point_set_to_infinity(VALUE self)
15231528

15241529
/*
15251530
* call-seq:
1526-
* point.to_bn(conversion_form = nil) => OpenSSL::BN
1531+
* point.to_octet_string(conversion_form) -> String
1532+
*
1533+
* Returns the octet string representation of the elliptic curve point.
15271534
*
1528-
* Convert the EC point into an octet string and store in an OpenSSL::BN. If
1529-
* _conversion_form_ is given, the point data is converted using the specified
1530-
* form. If not given, the default form set in the EC::Group object is used.
1535+
* _conversion_form_ specifies how the point is converted. Possible values are:
15311536
*
1532-
* See also EC::Point#point_conversion_form=.
1537+
* - +:compressed+
1538+
* - +:uncompressed+
1539+
* - +:hybrid+
15331540
*/
15341541
static VALUE
1535-
ossl_ec_point_to_bn(int argc, VALUE *argv, VALUE self)
1542+
ossl_ec_point_to_octet_string(VALUE self, VALUE conversion_form)
15361543
{
15371544
EC_POINT *point;
1538-
VALUE form_obj, bn_obj;
15391545
const EC_GROUP *group;
15401546
point_conversion_form_t form;
1541-
BIGNUM *bn;
1547+
VALUE str;
1548+
size_t len;
15421549

15431550
GetECPoint(self, point);
15441551
GetECPointGroup(self, group);
1545-
rb_scan_args(argc, argv, "01", &form_obj);
1546-
if (NIL_P(form_obj))
1547-
form = EC_GROUP_get_point_conversion_form(group);
1548-
else
1549-
form = parse_point_conversion_form_symbol(form_obj);
1550-
1551-
bn_obj = rb_obj_alloc(cBN);
1552-
bn = GetBNPtr(bn_obj);
1553-
1554-
if (EC_POINT_point2bn(group, point, form, bn, ossl_bn_ctx) == NULL)
1555-
ossl_raise(eEC_POINT, "EC_POINT_point2bn");
1556-
1557-
return bn_obj;
1552+
form = parse_point_conversion_form_symbol(conversion_form);
1553+
1554+
len = EC_POINT_point2oct(group, point, form, NULL, 0, ossl_bn_ctx);
1555+
if (!len)
1556+
ossl_raise(eEC_POINT, "EC_POINT_point2oct");
1557+
str = rb_str_new(NULL, (long)len);
1558+
if (!EC_POINT_point2oct(group, point, form,
1559+
(unsigned char *)RSTRING_PTR(str), len,
1560+
ossl_bn_ctx))
1561+
ossl_raise(eEC_POINT, "EC_POINT_point2oct");
1562+
return str;
15581563
}
15591564

15601565
/*
@@ -1779,7 +1784,7 @@ void Init_ossl_ec(void)
17791784
rb_define_method(cEC_POINT, "set_to_infinity!", ossl_ec_point_set_to_infinity, 0);
17801785
/* all the other methods */
17811786

1782-
rb_define_method(cEC_POINT, "to_bn", ossl_ec_point_to_bn, -1);
1787+
rb_define_method(cEC_POINT, "to_octet_string", ossl_ec_point_to_octet_string, 1);
17831788
rb_define_method(cEC_POINT, "mul", ossl_ec_point_mul, -1);
17841789

17851790
id_i_group = rb_intern("@group");

lib/openssl/pkey.rb

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
11
# frozen_string_literal: false
2-
module OpenSSL
2+
#--
3+
# Ruby/OpenSSL Project
4+
# Copyright (C) 2017 Ruby/OpenSSL Project Authors
5+
#++
6+
7+
module OpenSSL::PKey
8+
if defined?(EC)
9+
class EC::Point
10+
# :call-seq:
11+
# point.to_bn([conversion_form]) -> OpenSSL::BN
12+
#
13+
# Returns the octet string representation of the EC point as an instance of
14+
# OpenSSL::BN.
15+
#
16+
# If _conversion_form_ is not given, the _point_conversion_form_ attribute
17+
# set to the group is used.
18+
#
19+
# See #to_octet_string for more information.
20+
def to_bn(conversion_form = group.point_conversion_form)
21+
OpenSSL::BN.new(to_octet_string(conversion_form), 2)
22+
end
23+
end
24+
end
325
end

test/test_pkey_ec.rb

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,9 @@ def test_ECPrivateKey
120120
asn1 = OpenSSL::ASN1::Sequence([
121121
OpenSSL::ASN1::Integer(1),
122122
OpenSSL::ASN1::OctetString(p256.private_key.to_s(2)),
123-
OpenSSL::ASN1::ASN1Data.new(
124-
[OpenSSL::ASN1::ObjectId("prime256v1")],
125-
0, :CONTEXT_SPECIFIC
126-
),
127-
OpenSSL::ASN1::ASN1Data.new(
128-
[OpenSSL::ASN1::BitString(p256.public_key.to_bn.to_s(2))],
129-
1, :CONTEXT_SPECIFIC
130-
)
123+
OpenSSL::ASN1::ObjectId("prime256v1", 0, :EXPLICIT),
124+
OpenSSL::ASN1::BitString(p256.public_key.to_octet_string(:uncompressed),
125+
1, :EXPLICIT)
131126
])
132127
key = OpenSSL::PKey::EC.new(asn1.to_der)
133128
assert_predicate key, :private?
@@ -181,7 +176,7 @@ def test_PUBKEY
181176
OpenSSL::ASN1::ObjectId("prime256v1")
182177
]),
183178
OpenSSL::ASN1::BitString(
184-
p256.public_key.to_bn.to_s(2)
179+
p256.public_key.to_octet_string(:uncompressed)
185180
)
186181
])
187182
key = OpenSSL::PKey::EC.new(asn1.to_der)
@@ -224,7 +219,8 @@ def test_ec_group
224219
assert_equal :uncompressed, group4.point_conversion_form
225220
assert_equal group1, group4
226221
assert_equal group1.curve_name, group4.curve_name
227-
assert_equal group1.generator.to_bn, group4.generator.to_bn
222+
assert_equal group1.generator.to_octet_string(:uncompressed),
223+
group4.generator.to_octet_string(:uncompressed)
228224
assert_equal group1.order, group4.order
229225
assert_equal group1.cofactor, group4.cofactor
230226
assert_equal group1.seed, group4.seed
@@ -239,34 +235,57 @@ def test_ec_point
239235
point2 = OpenSSL::PKey::EC::Point.new(group, point.to_bn)
240236
assert_equal point, point2
241237
assert_equal point.to_bn, point2.to_bn
238+
assert_equal point.to_octet_string(:uncompressed),
239+
point2.to_octet_string(:uncompressed)
240+
241+
point3 = OpenSSL::PKey::EC::Point.new(group,
242+
point.to_octet_string(:uncompressed))
243+
assert_equal point, point3
244+
assert_equal point.to_bn, point3.to_bn
245+
assert_equal point.to_octet_string(:uncompressed),
246+
point3.to_octet_string(:uncompressed)
247+
242248
point2.invert!
243-
assert_not_equal point.to_bn, point2.to_bn
249+
point3.invert!
250+
assert_not_equal point.to_octet_string(:uncompressed),
251+
point2.to_octet_string(:uncompressed)
252+
assert_equal point2.to_octet_string(:uncompressed),
253+
point3.to_octet_string(:uncompressed)
244254

245255
begin
246256
group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2)
247257
group.point_conversion_form = :uncompressed
248-
generator = OpenSSL::PKey::EC::Point.new(group, 0x040501.to_bn)
258+
generator = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 }))
249259
group.set_generator(generator, 19, 1)
250-
point = OpenSSL::PKey::EC::Point.new(group, 0x040603.to_bn)
260+
point = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 }))
251261
rescue OpenSSL::PKey::EC::Group::Error
252262
pend "Patched OpenSSL rejected curve" if /unsupported field/ =~ $!.message
253263
raise
254264
end
255265

266+
assert_equal 0x040603.to_bn, point.to_bn
256267
assert_equal 0x040603.to_bn, point.to_bn(:uncompressed)
257268
assert_equal 0x0306.to_bn, point.to_bn(:compressed)
258269
assert_equal 0x070603.to_bn, point.to_bn(:hybrid)
259270

260-
assert_equal 0x040603.to_bn, point.to_bn
271+
group2 = group.dup; group2.point_conversion_form = :compressed
272+
point2 = OpenSSL::PKey::EC::Point.new(group2, B(%w{ 04 06 03 }))
273+
assert_equal 0x0306.to_bn, point2.to_bn
274+
275+
assert_equal B(%w{ 04 06 03 }), point.to_octet_string(:uncompressed)
276+
assert_equal B(%w{ 03 06 }), point.to_octet_string(:compressed)
277+
assert_equal B(%w{ 07 06 03 }), point.to_octet_string(:hybrid)
278+
261279
assert_equal true, point.on_curve?
262280
point.invert! # 8.5
263-
assert_equal 0x04060E.to_bn, point.to_bn
281+
assert_equal B(%w{ 04 06 0E }), point.to_octet_string(:uncompressed)
264282
assert_equal true, point.on_curve?
265283

266284
assert_equal false, point.infinity?
267285
point.set_to_infinity!
268286
assert_equal true, point.infinity?
269287
assert_equal 0.to_bn, point.to_bn
288+
assert_equal B(%w{ 00 }), point.to_octet_string(:uncompressed)
270289
assert_equal true, point.on_curve?
271290
end
272291

@@ -276,25 +295,25 @@ def test_ec_point_mul
276295
# generator is (5, 1)
277296
group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2)
278297
group.point_conversion_form = :uncompressed
279-
gen = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new("040501", 16))
298+
gen = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 }))
280299
group.set_generator(gen, 19, 1)
281300

282301
# 3 * (6, 3) = (16, 13)
283-
point_a = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new("040603", 16))
302+
point_a = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 }))
284303
result_a1 = point_a.mul(3)
285-
assert_equal("04100D", result_a1.to_bn.to_s(16))
304+
assert_equal B(%w{ 04 10 0D }), result_a1.to_octet_string(:uncompressed)
286305
# 3 * (6, 3) + 3 * (5, 1) = (7, 6)
287306
result_a2 = point_a.mul(3, 3)
288-
assert_equal("040706", result_a2.to_bn.to_s(16))
307+
assert_equal B(%w{ 04 07 06 }), result_a2.to_octet_string(:uncompressed)
289308
# 3 * point_a = 3 * (6, 3) = (16, 13)
290309
result_b1 = point_a.mul([3], [])
291-
assert_equal("04100D", result_b1.to_bn.to_s(16))
310+
assert_equal B(%w{ 04 10 0D }), result_b1.to_octet_string(:uncompressed)
292311
# 3 * point_a + 2 * point_a = 3 * (6, 3) + 2 * (6, 3) = (7, 11)
293312
result_b1 = point_a.mul([3, 2], [point_a])
294-
assert_equal("04070B", result_b1.to_bn.to_s(16))
313+
assert_equal B(%w{ 04 07 0B }), result_b1.to_octet_string(:uncompressed)
295314
# 3 * point_a + 5 * point_a.group.generator = 3 * (6, 3) + 5 * (5, 1) = (13, 10)
296315
result_b1 = point_a.mul([3], [], 5)
297-
assert_equal("040D0A", result_b1.to_bn.to_s(16))
316+
assert_equal B(%w{ 04 0D 0A }), result_b1.to_octet_string(:uncompressed)
298317
rescue OpenSSL::PKey::EC::Group::Error
299318
# CentOS patches OpenSSL to reject curves defined over Fp where p < 256 bits
300319
raise if $!.message !~ /unsupported field/
@@ -316,6 +335,10 @@ def test_ec_point_mul
316335

317336
private
318337

338+
def B(ary)
339+
[Array(ary).join].pack("H*")
340+
end
341+
319342
def assert_same_ec(expected, key)
320343
check_component(expected, key, [:group, :public_key, :private_key])
321344
end

0 commit comments

Comments
 (0)