Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create API to gather curve params from an OpenSSL::PKey::EC::Group #432

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 100 additions & 1 deletion ext/openssl/ossl_pkey_ec.c
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,101 @@ static VALUE ossl_ec_group_set_seed(VALUE self, VALUE seed)

/* get/set curve GFp, GF2m */



/*
* call-seq:
* group.field_type => Symbol or Integer
*
* See the OpenSSL documentation for EC_METHOD_get_field_type().
* If the field type is GFp (NID_X9_62_prime_field) it will return :GFp
* If the field type is GF2m (NID_X9_62_characteristic_two_field) it will return :GF2m
* If OpenSSL returns any other value it will return the raw NID_X9_62 value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I commented in #425, I'd like to avoid the usage of :GFp or :GF2m symbols and simply use OBJ_nid2sn().

*/
static VALUE ossl_ec_group_get_field_type(VALUE self)
{
EC_GROUP *group;
GetECGroup(self, group);
if (!group) {
rb_raise(eEC_GROUP, "EC_GROUP is not initialized");
}

const EC_METHOD *method = EC_GROUP_method_of(group);
int field_type_id = EC_METHOD_get_field_type(method);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable declarations need to be at the beginning of the block while we support Ruby <= 2.6.

if (field_type_id == NID_X9_62_prime_field) {
return ID2SYM(s_GFp);
} else if (field_type_id == NID_X9_62_characteristic_two_field) {
return ID2SYM(s_GF2m);
}

return INT2NUM(field_type_id);
rickmark marked this conversation as resolved.
Show resolved Hide resolved
}

/*
* call-seq:
* group.curve_params => Array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per #425, this returns Hash?

*
* See the OpenSSL documentation for EC_GROUP_get_curve_GFp() and EC_GROUP_set_curve_GF2m()
*/
static VALUE ossl_ec_group_get_curve_params(VALUE self)
{
EC_GROUP *group;
VALUE p, a, b, result, field_type;
BIGNUM *pBN, *aBN, *bBN;

GetECGroup(self, group);
if (!group) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!group will not happen.

rb_raise(eEC_GROUP, "EC_GROUP not initialized");
}

p = ossl_bn_new(NULL);
a = ossl_bn_new(NULL);
b = ossl_bn_new(NULL);

pBN = GetBNPtr(p);
aBN = GetBNPtr(a);
bBN = GetBNPtr(b);

result = rb_ary_new_capa(4);
field_type = SYM2ID(ossl_ec_group_get_field_type(self));

if (field_type == s_GFp) {
EC_GROUP_get_curve_GFp(group, pBN, aBN, bBN, NULL);
}
else if (field_type == s_GF2m) {
EC_GROUP_set_curve_GF2m(group, pBN, aBN, bBN, NULL);
}
else {
rb_raise(eEC_GROUP, "EC_GROUP field_type was not GFp or GF2m");
}

rb_ary_store(result, 0, ID2SYM(field_type));
rb_ary_store(result, 1, p);
rb_ary_store(result, 2, a);
rb_ary_store(result, 3, b);

return result;
}

/*
* call-seq:
* group.field => aBN
*
* See the OpenSSL documentation for EC_GROUP_get_curve_GFp() and EC_GROUP_set_curve_GF2m() for parameter *p
*/
static VALUE ossl_ec_group_get_field(VALUE self)
{
VALUE curve_params;

curve_params = ossl_ec_group_get_curve_params(self);

if (RARRAY_LEN(curve_params) < 2) {
rb_raise(cEC_GROUP, "EC_CURVE parameters malformed");
}

return rb_ary_entry(curve_params, 1);
}

/*
* call-seq:
* group.degree => integer
Expand Down Expand Up @@ -1628,7 +1723,11 @@ void Init_ossl_ec(void)
rb_define_method(cEC_GROUP, "seed", ossl_ec_group_get_seed, 0);
rb_define_method(cEC_GROUP, "seed=", ossl_ec_group_set_seed, 1);

/* get/set GFp, GF2m */
rb_define_method(cEC_GROUP, "field", ossl_ec_group_get_field, 0);
rb_define_method(cEC_GROUP, "field_type", ossl_ec_group_get_field_type, 0);
rb_define_method(cEC_GROUP, "curve_params", ossl_ec_group_get_curve_params, 0);

/* set GFp, GF2m */

rb_define_method(cEC_GROUP, "degree", ossl_ec_group_get_degree, 0);

Expand Down
174 changes: 1 addition & 173 deletions test/openssl/test_pkey_ec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -219,180 +219,8 @@ def test_PUBKEY
assert_equal pem, dup_public(p256).export
end

def test_ec_group
group1 = OpenSSL::PKey::EC::Group.new("prime256v1")
key1 = OpenSSL::PKey::EC.new(group1)
assert_equal group1, key1.group

group2 = OpenSSL::PKey::EC::Group.new(group1)
assert_equal group1.to_der, group2.to_der
assert_equal group1, group2
group2.asn1_flag ^=OpenSSL::PKey::EC::NAMED_CURVE
assert_not_equal group1.to_der, group2.to_der
assert_equal group1, group2

group3 = group1.dup
assert_equal group1.to_der, group3.to_der

assert group1.asn1_flag & OpenSSL::PKey::EC::NAMED_CURVE # our default
der = group1.to_der
group4 = OpenSSL::PKey::EC::Group.new(der)
group1.point_conversion_form = group4.point_conversion_form = :uncompressed
assert_equal :uncompressed, group1.point_conversion_form
assert_equal :uncompressed, group4.point_conversion_form
assert_equal group1, group4
assert_equal group1.curve_name, group4.curve_name
assert_equal group1.generator.to_octet_string(:uncompressed),
group4.generator.to_octet_string(:uncompressed)
assert_equal group1.order, group4.order
assert_equal group1.cofactor, group4.cofactor
assert_equal group1.seed, group4.seed
assert_equal group1.degree, group4.degree
end

def test_ec_point
group = OpenSSL::PKey::EC::Group.new("prime256v1")
key = OpenSSL::PKey::EC.new(group).generate_key!
point = key.public_key

point2 = OpenSSL::PKey::EC::Point.new(group, point.to_bn)
assert_equal point, point2
assert_equal point.to_bn, point2.to_bn
assert_equal point.to_octet_string(:uncompressed),
point2.to_octet_string(:uncompressed)

point3 = OpenSSL::PKey::EC::Point.new(group,
point.to_octet_string(:uncompressed))
assert_equal point, point3
assert_equal point.to_bn, point3.to_bn
assert_equal point.to_octet_string(:uncompressed),
point3.to_octet_string(:uncompressed)

point2.invert!
point3.invert!
assert_not_equal point.to_octet_string(:uncompressed),
point2.to_octet_string(:uncompressed)
assert_equal point2.to_octet_string(:uncompressed),
point3.to_octet_string(:uncompressed)

begin
group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2)
group.point_conversion_form = :uncompressed
generator = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 }))
group.set_generator(generator, 19, 1)
point = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 }))
rescue OpenSSL::PKey::EC::Group::Error
pend "Patched OpenSSL rejected curve" if /unsupported field/ =~ $!.message
raise
end

assert_equal 0x040603.to_bn, point.to_bn
assert_equal 0x040603.to_bn, point.to_bn(:uncompressed)
assert_equal 0x0306.to_bn, point.to_bn(:compressed)
assert_equal 0x070603.to_bn, point.to_bn(:hybrid)

group2 = group.dup; group2.point_conversion_form = :compressed
point2 = OpenSSL::PKey::EC::Point.new(group2, B(%w{ 04 06 03 }))
assert_equal 0x0306.to_bn, point2.to_bn

assert_equal B(%w{ 04 06 03 }), point.to_octet_string(:uncompressed)
assert_equal B(%w{ 03 06 }), point.to_octet_string(:compressed)
assert_equal B(%w{ 07 06 03 }), point.to_octet_string(:hybrid)

assert_equal true, point.on_curve?
point.invert! # 8.5
assert_equal B(%w{ 04 06 0E }), point.to_octet_string(:uncompressed)
assert_equal true, point.on_curve?

assert_equal false, point.infinity?
point.set_to_infinity!
assert_equal true, point.infinity?
assert_equal 0.to_bn, point.to_bn
assert_equal B(%w{ 00 }), point.to_octet_string(:uncompressed)
assert_equal true, point.on_curve?
end

def test_ec_point_add
begin
group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2)
group.point_conversion_form = :uncompressed
gen = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 }))
group.set_generator(gen, 19, 1)

point_a = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 }))
point_b = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 10 0D }))
rescue OpenSSL::PKey::EC::Group::Error
pend "Patched OpenSSL rejected curve" if /unsupported field/ =~ $!.message
raise
end

result = point_a.add(point_b)
assert_equal B(%w{ 04 0D 07 }), result.to_octet_string(:uncompressed)

assert_raise(TypeError) { point_a.add(nil) }
assert_raise(ArgumentError) { point_a.add }
end

def test_ec_point_mul
begin
# y^2 = x^3 + 2x + 2 over F_17
# generator is (5, 1)
group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2)
group.point_conversion_form = :uncompressed
gen = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 }))
group.set_generator(gen, 19, 1)

# 3 * (6, 3) = (16, 13)
point_a = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 }))
result_a1 = point_a.mul(3)
assert_equal B(%w{ 04 10 0D }), result_a1.to_octet_string(:uncompressed)
# 3 * (6, 3) + 3 * (5, 1) = (7, 6)
result_a2 = point_a.mul(3, 3)
assert_equal B(%w{ 04 07 06 }), result_a2.to_octet_string(:uncompressed)
EnvUtil.suppress_warning do # Point#mul(ary, ary [, bn]) is deprecated
begin
result_b1 = point_a.mul([3], [])
rescue NotImplementedError
# LibreSSL and OpenSSL 3.0 do no longer support this form of calling
next
end

# 3 * point_a = 3 * (6, 3) = (16, 13)
result_b1 = point_a.mul([3], [])
assert_equal B(%w{ 04 10 0D }), result_b1.to_octet_string(:uncompressed)
# 3 * point_a + 2 * point_a = 3 * (6, 3) + 2 * (6, 3) = (7, 11)
result_b1 = point_a.mul([3, 2], [point_a])
assert_equal B(%w{ 04 07 0B }), result_b1.to_octet_string(:uncompressed)
# 3 * point_a + 5 * point_a.group.generator = 3 * (6, 3) + 5 * (5, 1) = (13, 10)
result_b1 = point_a.mul([3], [], 5)
assert_equal B(%w{ 04 0D 0A }), result_b1.to_octet_string(:uncompressed)

assert_raise(ArgumentError) { point_a.mul([1], [point_a]) }
assert_raise(TypeError) { point_a.mul([1], nil) }
assert_raise(TypeError) { point_a.mul([nil], []) }
end
rescue OpenSSL::PKey::EC::Group::Error
# CentOS patches OpenSSL to reject curves defined over Fp where p < 256 bits
raise if $!.message !~ /unsupported field/
end

p256_key = Fixtures.pkey("p256")
p256_g = p256_key.group
assert_equal(p256_key.public_key, p256_g.generator.mul(p256_key.private_key))

# invalid argument
point = p256_key.public_key
assert_raise(TypeError) { point.mul(nil) }
end

# test Group: asn1_flag, point_conversion

private

def B(ary)
[Array(ary).join].pack("H*")
end


def assert_same_ec(expected, key)
check_component(expected, key, [:group, :public_key, :private_key])
end
Expand Down
61 changes: 61 additions & 0 deletions test/openssl/test_pkey_ec_group.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true
require_relative 'utils'

if defined?(OpenSSL) && defined?(OpenSSL::PKey::EC)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this came from test_pkey_ec.rb, but this is redundant. While you are at it, can you update it (and other occurrences in test_pkey_ec*.rb) to simply

Suggested change
if defined?(OpenSSL) && defined?(OpenSSL::PKey::EC)
if defined?(OpenSSL::PKey::EC)


class OpenSSL::TestECGroup < OpenSSL::PKeyTestCase
SECP256K1_NAME = 'secp256k1'
SECP256K1_FIELD = OpenSSL::BN.new "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16
SECP256K1_A = 0.to_bn
SECP256K1_B = 7.to_bn
SECP256K1_G_COMPRESSED = OpenSSL::BN.new "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16
SECP256K1_G_UNCOMPRESSED = OpenSSL::BN.new "0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" \
"483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16
SECP256K1_ORDER = OpenSSL::BN.new "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16
SECP256K1_COFACTOR = 1
SECP256K1_DEGREE = 256

def test_ec_group
group1 = OpenSSL::PKey::EC::Group.new("prime256v1")
key1 = OpenSSL::PKey::EC.new(group1)
assert_equal group1, key1.group

group2 = OpenSSL::PKey::EC::Group.new(group1)
assert_equal group1.to_der, group2.to_der
assert_equal group1, group2
group2.asn1_flag ^=OpenSSL::PKey::EC::NAMED_CURVE
assert_not_equal group1.to_der, group2.to_der
assert_equal group1, group2

group3 = group1.dup
assert_equal group1.to_der, group3.to_der

assert group1.asn1_flag & OpenSSL::PKey::EC::NAMED_CURVE # our default
der = group1.to_der
group4 = OpenSSL::PKey::EC::Group.new(der)
group1.point_conversion_form = group4.point_conversion_form = :uncompressed
assert_equal :uncompressed, group1.point_conversion_form
assert_equal :uncompressed, group4.point_conversion_form
assert_equal group1, group4
assert_equal group1.curve_name, group4.curve_name
assert_equal group1.generator.to_octet_string(:uncompressed),
group4.generator.to_octet_string(:uncompressed)
assert_equal group1.order, group4.order
assert_equal group1.cofactor, group4.cofactor
assert_equal group1.seed, group4.seed
assert_equal group1.degree, group4.degree
end

def test_get_group_parameters
group = OpenSSL::PKey::EC::Group.new SECP256K1_NAME

assert_equal(SECP256K1_NAME, group.curve_name)
assert_equal(SECP256K1_ORDER, group.order)
assert_equal(SECP256K1_COFACTOR, group.cofactor)
assert_equal(SECP256K1_DEGREE, group.degree)
assert_equal(SECP256K1_FIELD, group.field)
assert_equal(:GFp, group.field_type)
assert_equal([:GFp, SECP256K1_FIELD, SECP256K1_A, SECP256K1_B], group.curve_params)
end
end
end
Loading