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

Introduced optional values to ECDSA sign for deterministic k values #434

Open
wants to merge 4 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
137 changes: 130 additions & 7 deletions ext/openssl/ossl_pkey_ec.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ static ID id_i_group;

static VALUE ec_group_new(const EC_GROUP *group);
static VALUE ec_point_new(const EC_POINT *point, const EC_GROUP *group);
static VALUE ossl_ec_group_gen_ecdsa_params(VALUE self, VALUE k);

/*
* Creates a new EC_KEY on the EC group obj. arg can be an EC::Group or a String
Expand Down Expand Up @@ -485,24 +486,54 @@ static VALUE ossl_ec_key_check_key(VALUE self)
/*
* call-seq:
* key.dsa_sign_asn1(data) => String
* key.dsa_sign_asn1(data, k) => String
* key.dsa_sign_asn1(data, inverse_k, r) => String
*
* See the OpenSSL documentation for ECDSA_sign()
* See the OpenSSL documentation for ECDSA_sign() and ECDSA_sign_ex()
*/
static VALUE ossl_ec_key_dsa_sign_asn1(VALUE self, VALUE data)
static VALUE ossl_ec_key_dsa_sign_asn1(int argc, VALUE argv[], VALUE self)
{
EC_KEY *ec;
BIGNUM *invkBN, *rBN;
unsigned int buf_len;
VALUE str;
VALUE str, group, params, r, inverse_k;

GetEC(self, ec);
StringValue(data);
StringValue(argv[0]);

if (EC_KEY_get0_private_key(ec) == NULL)
ossl_raise(eECError, "Private EC key needed!");

str = rb_str_new(0, ECDSA_size(ec));
if (ECDSA_sign(0, (unsigned char *) RSTRING_PTR(data), RSTRING_LENINT(data), (unsigned char *) RSTRING_PTR(str), &buf_len, ec) != 1)
ossl_raise(eECError, "ECDSA_sign");
if (argc == 1) {
if (ECDSA_sign(0, (unsigned char *) RSTRING_PTR(argv[0]), RSTRING_LENINT(argv[0]), (unsigned char *) RSTRING_PTR(str), &buf_len, ec) != 1)
ossl_raise(eECError, "ECDSA_sign");
}
else if (argc == 2 || argc == 3) {
if (argc == 2) {
group = ossl_ec_key_get_group(self);
params = ossl_ec_group_gen_ecdsa_params(group, argv[1]);
inverse_k = RARRAY_AREF(params, 0);
invkBN = GetBNPtr(inverse_k);
r = RARRAY_AREF(params, 1);
rBN = GetBNPtr(r);
}
else {
invkBN = GetBNPtr(argv[1]);
rBN = GetBNPtr(argv[2]);
}

if (invkBN == NULL || rBN == NULL) {
rb_raise(eECError, "inverse_k and r must both be OpenSSL::BN");
}

if (ECDSA_sign_ex(0, (unsigned char *) RSTRING_PTR(argv[0]), RSTRING_LENINT(argv[0]), (unsigned char *) RSTRING_PTR(str), &buf_len, invkBN, rBN, ec) != 1)
ossl_raise(eECError, "ECDSA_sign");
}
else {
rb_raise(eECError, "invalid arguments");
}

rb_str_set_len(str, buf_len);

return str;
Expand Down Expand Up @@ -1521,6 +1552,97 @@ static VALUE ossl_ec_point_mul(int argc, VALUE *argv, VALUE self)
return result;
}


/*
* Private method: creates inverse_k and r from k suitable for ECDSA
*
* call-seq:
* group.ecdsa_generate_signature_params(k) => [ inverse_k, r ]
*
* Params:
* k : a OpenSSL::BN in the range of 0 < k < order
*
* Return: two values in a decomposable array
* inverse_k : the multiplicative inverse of k
* r : the X value for the ephemeral point R
*
* This uses `k` as the ephemeral private key to generate point R.
* The return is an array with the multiplicative inverse of k known as
* `inverse_k` and the X value of point R known as `r`. These are used
* as parameters to the ECDSA signature operation where a random `k` is
* not desired such as rfc6979
*
*/
static VALUE ossl_ec_group_gen_ecdsa_params(VALUE self, VALUE k)
{
VALUE result, order, point_r, inverse_k, r, reduceR;
BIGNUM *kBN, *rBN, *invkBN, *orderBN, *reduceBN;
BN_CTX *bnCtx;
const EC_GROUP *ecGroup;
EC_POINT *pointR;

if (NIL_P(k)) {
rb_raise(eECError, "value for k must not be nil");
}

kBN = GetBNPtr(k);
if (kBN == NULL) {
rb_raise(eECError, "value for k must be an OpenSSL::BN");
}

order = ossl_ec_group_get_order(self);
orderBN = GetBNPtr(order);

if (BN_cmp(kBN, orderBN) == 1) {
rb_raise(eECError, "value for k must be less then group order");
}

GetECGroup(self, ecGroup);
point_r = ossl_ec_point_alloc(cEC_POINT);
point_r = ossl_ec_point_initialize(1, &self, point_r);
GetECPoint(point_r, pointR);

if (pointR == NULL) {
rb_raise(eECError, "unable to allocate point R");
}

if (!EC_POINT_mul(ecGroup, pointR, kBN, NULL, NULL, NULL)) {
rb_raise(eECError, "unable to multiply generator by k to produce R");
}

r = ossl_bn_new(NULL);
rBN = GetBNPtr(r);

if (!EC_POINT_get_affine_coordinates(ecGroup, pointR, rBN, NULL, NULL)) {
rb_raise(eECError, "unable to get coordinates for R");
}

reduceR = ossl_bn_new(NULL);
reduceBN = GetBNPtr(reduceR);

bnCtx = BN_CTX_new();

if (!BN_nnmod(reduceBN, rBN, orderBN, bnCtx)) {
BN_CTX_free(bnCtx);
rb_raise(eECError, "unable to reduce r to order");
}

inverse_k = ossl_bn_new(NULL);
invkBN = GetBNPtr(inverse_k);
if (!BN_mod_inverse(invkBN, kBN, orderBN, bnCtx)) {
BN_CTX_free(bnCtx);
rb_raise(eECError, "unable to inverse k");
}

BN_CTX_free(bnCtx);

result = rb_ary_new_capa(2);
rb_ary_store(result, 0, inverse_k);
rb_ary_store(result, 1, reduceR);

return result;
}

void Init_ossl_ec(void)
{
#undef rb_intern
Expand Down Expand Up @@ -1594,7 +1716,7 @@ void Init_ossl_ec(void)
rb_define_alias(cEC, "generate_key", "generate_key!");
rb_define_method(cEC, "check_key", ossl_ec_key_check_key, 0);

rb_define_method(cEC, "dsa_sign_asn1", ossl_ec_key_dsa_sign_asn1, 1);
rb_define_method(cEC, "dsa_sign_asn1", ossl_ec_key_dsa_sign_asn1, -1);
rb_define_method(cEC, "dsa_verify_asn1", ossl_ec_key_dsa_verify_asn1, 2);
/* do_sign/do_verify */

Expand Down Expand Up @@ -1638,6 +1760,7 @@ void Init_ossl_ec(void)
rb_define_method(cEC_GROUP, "to_pem", ossl_ec_group_to_pem, 0);
rb_define_method(cEC_GROUP, "to_der", ossl_ec_group_to_der, 0);
rb_define_method(cEC_GROUP, "to_text", ossl_ec_group_to_text, 0);
rb_define_private_method(cEC_GROUP, "ecdsa_generate_signature_params", ossl_ec_group_gen_ecdsa_params, 1);


rb_define_alloc_func(cEC_POINT, ossl_ec_point_alloc);
Expand Down
70 changes: 70 additions & 0 deletions test/openssl/fixtures/ecdsa/prime192v1.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
curve: prime192v1
parameters:
q: FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831
key:
private: 6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4
public:
x: AC2C77F529F91689FEA0EA5EFEC7F210D8EEA0B9E047ED56
y: 3BC723E57670BD4887EBC732C523063D0A7C957BC97C1C43
examples:
- message: "sample"
hash: SHA1
nonce: 37D7CA00D2C7B0E5E412AC03BD44BA837FDD5B28CD3B0021
signature:
r: 98C6BD12B23EAF5E2A2045132086BE3EB8EBD62ABF6698FF
s: 57A22B07DEA9530F8DE9471B1DC6624472E8E2844BC25B64
- message: "sample"
hash: SHA224
nonce: 4381526B3FC1E7128F202E194505592F01D5FF4C5AF015D8
signature:
r: A1F00DAD97AEEC91C95585F36200C65F3C01812AA60378F5
s: E07EC1304C7C6C9DEBBE980B9692668F81D4DE7922A0F97A
- message: "sample"
hash: SHA256
nonce: 32B1B6D7D42A05CB449065727A84804FB1A3E34D8F261496
signature:
r: 4B0B8CE98A92866A2820E20AA6B75B56382E0F9BFD5ECB55
s: CCDB006926EA9565CBADC840829D8C384E06DE1F1E381B85
- message: "sample"
hash: SHA384
nonce: 4730005C4FCB01834C063A7B6760096DBE284B8252EF4311
signature:
r: DA63BF0B9ABCF948FBB1E9167F136145F7A20426DCC287D5
s: C3AA2C960972BD7A2003A57E1C4C77F0578F8AE95E31EC5E
- message: "sample"
hash: SHA512
nonce: A2AC7AB055E4F20692D49209544C203A7D1F2C0BFBC75DB1
signature:
r: 4D60C5AB1996BD848343B31C00850205E2EA6922DAC2E4B8
s: 3F6E837448F027A1BF4B34E796E32A811CBB4050908D8F67
- message: "test"
hash: SHA1
nonce: D9CF9C3D3297D3260773A1DA7418DB5537AB8DD93DE7FA25
signature:
r: 0F2141A0EBBC44D2E1AF90A50EBCFCE5E197B3B7D4DE036D
s: EB18BC9E1F3D7387500CB99CF5F7C157070A8961E38700B7
- message: "test"
hash: SHA224
nonce: F5DC805F76EF851800700CCE82E7B98D8911B7D510059FBE
signature:
r: 6945A1C1D1B2206B8145548F633BB61CEF04891BAF26ED34
s: B7FB7FDFC339C0B9BD61A9F5A8EAF9BE58FC5CBA2CB15293
- message: "test"
hash: SHA256
nonce: 5C4CE89CF56D9E7C77C8585339B006B97B5F0680B4306C6C
signature:
r: 3A718BD8B4926C3B52EE6BBE67EF79B18CB6EB62B1AD97AE
s: 5662E6848A4A19B1F1AE2F72ACD4B8BBE50F1EAC65D9124F
- message: "test"
hash: SHA384
nonce: 5AFEFB5D3393261B828DB6C91FBC68C230727B030C975693
signature:
r: B234B60B4DB75A733E19280A7A6034BD6B1EE88AF5332367
s: 7994090B2D59BB782BE57E74A44C9A1C700413F8ABEFE77A
- message: "test"
hash: SHA512
nonce: 0758753A5254759C7CFBAD2E2D9B0792EEE44136C9480527
signature:
r: FE4F4AE86A58B6507946715934FE2D8FF9D95B6B098FE739
s: 74CF5605C98FBA0E1EF34D4B5A1577A7DCF59457CAE52290
78 changes: 78 additions & 0 deletions test/openssl/test_pkey_ec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,84 @@ def test_ec_point_mul
assert_raise(TypeError) { point.mul(nil) }
end

def test_ecdsa_generate_from_k
group = OpenSSL::PKey::EC::Group.new 'prime192v1'

k = OpenSSL::BN.new '0758753A5254759C7CFBAD2E2D9B0792EEE44136C9480527', 16
expected_r = OpenSSL::BN.new 'FE4F4AE86A58B6507946715934FE2D8FF9D95B6B098FE739', 16

inverse_k, r = group.send(:ecdsa_generate_signature_params, k)

assert_equal(expected_r, r)
assert_not_equal(inverse_k, k)
assert_equal(0.to_bn, group.order.mod_mul(inverse_k, group.order))
end

def test_ecdsa_deterministic_k
group = OpenSSL::PKey::EC::Group.new 'prime192v1'

private_key = OpenSSL::BN.new '6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4', 16
public_key_encoded = OpenSSL::BN.new '04AC2C77F529F91689FEA0EA5EFEC7F210D8EEA0B9E047ED56' \
'3BC723E57670BD4887EBC732C523063D0A7C957BC97C1C43', 16
public_key = OpenSSL::PKey::EC::Point.new group, public_key_encoded
key = OpenSSL::PKey::EC.new group
key.private_key = private_key
key.public_key = public_key

hash = OpenSSL::Digest.new('SHA512').digest('test')

k = OpenSSL::BN.new '0758753A5254759C7CFBAD2E2D9B0792EEE44136C9480527', 16
expected_r = OpenSSL::BN.new 'FE4F4AE86A58B6507946715934FE2D8FF9D95B6B098FE739', 16
expected_s = OpenSSL::BN.new '74CF5605C98FBA0E1EF34D4B5A1577A7DCF59457CAE52290', 16

inverse_k, r = group.send(:ecdsa_generate_signature_params, k)

result = key.dsa_sign_asn1(hash, inverse_k, r)
assert_equal(true, key.dsa_verify_asn1(hash, result))

k_only_result = key.dsa_sign_asn1(hash, k)
assert_equal(true, key.dsa_verify_asn1(hash, k_only_result))

parsed_signature = OpenSSL::ASN1.decode(result)
assert_equal(expected_r, parsed_signature.value[0].value)
assert_equal(expected_s, parsed_signature.value[1].value)
end

def test_ecdsa_raw_sign
group = OpenSSL::PKey::EC::Group.new 'prime192v1'

private_key = OpenSSL::BN.new '6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4', 16
public_key_encoded = OpenSSL::BN.new '04AC2C77F529F91689FEA0EA5EFEC7F210D8EEA0B9E047ED56' \
'3BC723E57670BD4887EBC732C523063D0A7C957BC97C1C43', 16
public_key = OpenSSL::PKey::EC::Point.new group, public_key_encoded
key = OpenSSL::PKey::EC.new group
key.private_key = private_key
key.public_key = public_key

hash = OpenSSL::Digest.new('SHA256').digest('test')
degree_bytes = group.degree / 8

k = OpenSSL::BN.new '5C4CE89CF56D9E7C77C8585339B006B97B5F0680B4306C6C', 16
expected_r = OpenSSL::BN.new '3A718BD8B4926C3B52EE6BBE67EF79B18CB6EB62B1AD97AE', 16
expected_s = OpenSSL::BN.new '5662E6848A4A19B1F1AE2F72ACD4B8BBE50F1EAC65D9124F', 16

inverse_k, r = group.send(:ecdsa_generate_signature_params, k)
assert_equal(expected_r, r)

result = key.dsa_sign_asn1(hash, inverse_k, r)
assert_equal(true, key.dsa_verify_asn1(hash, result))

# TODO: pad to degree when hash is smaller
e = OpenSSL::BN.new hash[0..(degree_bytes - 1)], 2
assert_equal(group.degree, e.num_bits)
s = inverse_k.mod_mul(e + (r * private_key), group.order)
assert_equal(expected_s, s)

parsed_signature = OpenSSL::ASN1.decode(result)
assert_equal(expected_r, parsed_signature.value[0].value)
assert_equal(expected_s, parsed_signature.value[1].value)
end

# test Group: asn1_flag, point_conversion

private
Expand Down
Loading