Skip to content

Commit a56fcb5

Browse files
committed
[feat] implement missing PKey::EC#dsa_verify_asn1
1 parent 7a7d3bc commit a56fcb5

File tree

3 files changed

+98
-46
lines changed

3 files changed

+98
-46
lines changed

src/main/java/org/jruby/ext/openssl/PKeyEC.java

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.security.KeyFactory;
1717
import java.security.KeyPair;
1818
import java.security.KeyPairGenerator;
19+
import java.security.MessageDigest;
1920
import java.security.NoSuchAlgorithmException;
2021
import java.security.PrivateKey;
2122
import java.security.PublicKey;
@@ -31,19 +32,26 @@
3132
import java.security.spec.ECPublicKeySpec;
3233
import java.security.spec.EllipticCurve;
3334
import java.security.spec.InvalidKeySpecException;
35+
import java.security.spec.InvalidParameterSpecException;
3436
import java.util.Collections;
3537
import java.util.Enumeration;
3638
import java.util.List;
3739
import javax.crypto.KeyAgreement;
3840
import org.bouncycastle.asn1.ASN1EncodableVector;
41+
import org.bouncycastle.asn1.ASN1Encoding;
42+
import org.bouncycastle.asn1.ASN1InputStream;
3943
import org.bouncycastle.asn1.ASN1Integer;
4044
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
4145
import org.bouncycastle.asn1.ASN1OutputStream;
42-
import org.bouncycastle.asn1.DLSequence;
46+
import org.bouncycastle.asn1.ASN1Primitive;
47+
import org.bouncycastle.asn1.ASN1Sequence;
48+
import org.bouncycastle.asn1.DERSequence;
4349

44-
import org.bouncycastle.crypto.params.ECNamedDomainParameters;
50+
import org.bouncycastle.crypto.params.ECDomainParameters;
4551
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
52+
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
4653
import org.bouncycastle.crypto.signers.ECDSASigner;
54+
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
4755
import org.bouncycastle.jce.ECNamedCurveTable;
4856
import org.bouncycastle.jce.ECPointUtil;
4957
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
@@ -110,10 +118,14 @@ static RubyClass _EC(final Ruby runtime) {
110118
return _PKey(runtime).getClass("EC");
111119
}
112120

113-
public static RaiseException newECError(Ruby runtime, String message) {
121+
private static RaiseException newECError(Ruby runtime, String message) {
114122
return Utils.newError(runtime, _PKey(runtime).getClass("ECError"), message);
115123
}
116124

125+
private static RaiseException newECError(Ruby runtime, String message, Exception cause) {
126+
return Utils.newError(runtime, _PKey(runtime).getClass("ECError"), message, cause);
127+
}
128+
117129
@JRubyMethod(meta = true)
118130
public static RubyArray builtin_curves(ThreadContext context, IRubyObject self) {
119131
final Ruby runtime = context.runtime;
@@ -396,7 +408,6 @@ public IRubyObject check_key(final ThreadContext context) {
396408

397409
@JRubyMethod(name = "generate_key")
398410
public PKeyEC generate_key(final ThreadContext context) {
399-
// final ECDomainParameters params = getDomainParameters();
400411
try {
401412
ECGenParameterSpec genSpec = new ECGenParameterSpec(getCurveName());
402413
KeyPairGenerator gen = SecurityHelper.getKeyPairGenerator("EC"); // "BC"
@@ -426,54 +437,69 @@ public static IRubyObject generate(final ThreadContext context, final IRubyObjec
426437

427438
@JRubyMethod(name = "dsa_sign_asn1")
428439
public IRubyObject dsa_sign_asn1(final ThreadContext context, final IRubyObject data) {
440+
if (privateKey == null) {
441+
throw newECError(context.runtime, "Private EC key needed!");
442+
}
429443
try {
430-
ECNamedCurveParameterSpec params = getParameterSpec();
431-
ASN1ObjectIdentifier oid = getCurveOID(getCurveName());
432-
ECNamedDomainParameters domainParams = new ECNamedDomainParameters(oid,
433-
params.getCurve(), params.getG(), params.getN(), params.getH(), params.getSeed()
434-
);
444+
final ECNamedCurveParameterSpec params = getParameterSpec();
435445

436446
final ECDSASigner signer = new ECDSASigner();
437-
final ECPrivateKey privKey = (ECPrivateKey) this.privateKey;
438-
signer.init(true, new ECPrivateKeyParameters(privKey.getS(), domainParams));
447+
signer.init(true, new ECPrivateKeyParameters(
448+
((ECPrivateKey) this.privateKey).getS(),
449+
new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH())
450+
));
439451

440-
final byte[] message = data.convertToString().getBytes();
441-
BigInteger[] signature = signer.generateSignature(message); // [r, s]
442-
443-
// final byte[] r = signature[0].toByteArray();
444-
// final byte[] s = signature[1].toByteArray();
445-
// // ASN.1 encode as: 0x30 len 0x02 rlen (r) 0x02 slen (s)
446-
// final int len = 1 + (1 + r.length) + 1 + (1 + s.length);
447-
//
448-
// final byte[] encoded = new byte[1 + 1 + len]; int i;
449-
// encoded[0] = 0x30;
450-
// encoded[1] = (byte) len;
451-
// encoded[2] = 0x20;
452-
// encoded[3] = (byte) r.length;
453-
// System.arraycopy(r, 0, encoded, i = 4, r.length); i += r.length;
454-
// encoded[i++] = 0x20;
455-
// encoded[i++] = (byte) s.length;
456-
// System.arraycopy(s, 0, encoded, i, s.length);
452+
BigInteger[] signature = signer.generateSignature(data.convertToString().getBytes()); // [r, s]
457453

458454
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
459-
ASN1OutputStream asn1 = ASN1OutputStream.create(bytes);
455+
ASN1OutputStream asn1 = ASN1OutputStream.create(bytes, ASN1Encoding.DER);
460456

461-
ASN1EncodableVector v = new ASN1EncodableVector();
457+
ASN1EncodableVector v = new ASN1EncodableVector(2);
462458
v.add(new ASN1Integer(signature[0])); // r
463459
v.add(new ASN1Integer(signature[1])); // s
464460

465-
asn1.writeObject(new DLSequence(v));
461+
asn1.writeObject(new DERSequence(v));
462+
asn1.close();
466463

467464
return StringHelper.newString(context.runtime, bytes.buffer(), bytes.size());
468465
}
469466
catch (IOException ex) {
470467
throw newECError(context.runtime, ex.getMessage());
471468
}
472-
catch (RuntimeException ex) {
473-
throw (RaiseException) newECError(context.runtime, ex.toString()).initCause(ex);
469+
catch (Exception ex) {
470+
throw newECError(context.runtime, ex.toString(), ex);
474471
}
475472
}
476473

474+
@JRubyMethod(name = "dsa_verify_asn1")
475+
public IRubyObject dsa_verify_asn1(final ThreadContext context, final IRubyObject data, final IRubyObject sign) {
476+
final Ruby runtime = context.runtime;
477+
try {
478+
final ECNamedCurveParameterSpec params = getParameterSpec();
479+
480+
final ECDSASigner signer = new ECDSASigner();
481+
signer.init(false, new ECPublicKeyParameters(
482+
EC5Util.convertPoint(publicKey.getParams(), publicKey.getW()),
483+
new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH())
484+
));
485+
486+
ASN1Primitive vec = new ASN1InputStream(sign.convertToString().getBytes()).readObject();
487+
488+
if (!(vec instanceof ASN1Sequence)) {
489+
throw newECError(runtime, "invalid signature (not a sequence)");
490+
}
491+
492+
ASN1Sequence seq = (ASN1Sequence) vec;
493+
ASN1Integer r = ASN1Integer.getInstance(seq.getObjectAt(0));
494+
ASN1Integer s = ASN1Integer.getInstance(seq.getObjectAt(1));
495+
496+
boolean verify = signer.verifySignature(data.convertToString().getBytes(), r.getPositiveValue(), s.getPositiveValue());
497+
return runtime.newBoolean(verify);
498+
}
499+
catch (IOException|IllegalArgumentException|IllegalStateException ex) {
500+
throw newECError(runtime, "invalid signature: " + ex.getMessage(), ex);
501+
}
502+
}
477503

478504
@JRubyMethod(name = "dh_compute_key")
479505
public IRubyObject dh_compute_key(final ThreadContext context, final IRubyObject point) {

src/test/ruby/ec/test_ec.rb

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -237,15 +237,18 @@ def test_check_key
237237
end
238238

239239
def test_sign_verify
240-
key_file = File.join(File.dirname(__FILE__), 'private_key.pem')
241-
242-
key = OpenSSL::PKey::EC.new(File.read(key_file))
243-
data = 'abcd'
244-
digest = OpenSSL::Digest::SHA256.new
245-
sig = key.sign(digest, data)
246-
assert_true key.verify(digest, sig, data)
247-
248-
key.sign(OpenSSL::Digest::SHA1.new, data)
240+
p256 = Fixtures.pkey("p256.pem")
241+
data = "Sign me!"
242+
signature = p256.sign("SHA1", data)
243+
assert_equal true, p256.verify("SHA1", signature, data)
244+
245+
signature0 = (<<~'end;').unpack("m")[0]
246+
MEQCIEOTY/hD7eI8a0qlzxkIt8LLZ8uwiaSfVbjX2dPAvN11AiAQdCYx56Fq
247+
QdBp1B4sxJoA8jvODMMklMyBKVmudboA6A==
248+
end;
249+
assert_equal true, p256.verify("SHA256", signature0, data)
250+
signature1 = signature0.succ
251+
assert_equal false, p256.verify("SHA256", signature1, data)
249252
end
250253

251254
def test_group_encoding
@@ -293,13 +296,31 @@ def test_set_keys
293296
end
294297
end if false # NOT-IMPLEMENTED TODO
295298

296-
def test_dsa_sign_verify
297-
data1 = 'foo'
299+
def test_dsa_sign_verify_all
300+
data1 = 'hashed-value'
298301
for key in @keys
302+
next if key.group.curve_name == 'SM2'
303+
299304
sig = key.dsa_sign_asn1(data1)
300-
assert(key.dsa_verify_asn1(data1, sig))
305+
assert_equal(true, key.dsa_verify_asn1(data1, sig))
306+
assert_equal(false, key.dsa_verify_asn1(data1 + 'X', sig))
301307
end
302-
end if false # NOT-IMPLEMENTED
308+
end
309+
310+
def test_sign_verify_raw
311+
key = Fixtures.pkey("p256.pem")
312+
data1 = "foo"
313+
data2 = "bar"
314+
315+
malformed_sig = "*" * 30
316+
317+
# Sign by #dsa_sign_asn1
318+
sig = key.dsa_sign_asn1(data1)
319+
320+
assert_equal true, key.dsa_verify_asn1(data1, sig)
321+
assert_equal false, key.dsa_verify_asn1(data2, sig)
322+
assert_raise(OpenSSL::PKey::ECError) { key.dsa_verify_asn1(data1, malformed_sig) }
323+
end
303324

304325
# def test_dh_compute_key
305326
# for key in @keys

src/test/ruby/fixtures/pkey/p256.pem

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEIID49FDqcf1O1eO8saTgG70UbXQw9Fqwseliit2aWhH1oAoGCCqGSM49
3+
AwEHoUQDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7lSZ/ArK41eGy5wAzU/0G51Xtt
4+
CeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg==
5+
-----END EC PRIVATE KEY-----

0 commit comments

Comments
 (0)