From a59ebbfbd194c6073536b85b17792fd5820f5e46 Mon Sep 17 00:00:00 2001 From: kares Date: Tue, 13 Feb 2024 14:30:26 +0100 Subject: [PATCH] [feat] implement missing PKey::EC#dsa_verify_asn1 --- .../java/org/jruby/ext/openssl/PKeyEC.java | 92 ++++++++++++------- src/test/ruby/ec/test_ec.rb | 47 +++++++--- 2 files changed, 93 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/jruby/ext/openssl/PKeyEC.java b/src/main/java/org/jruby/ext/openssl/PKeyEC.java index 45466e03..a70398a8 100644 --- a/src/main/java/org/jruby/ext/openssl/PKeyEC.java +++ b/src/main/java/org/jruby/ext/openssl/PKeyEC.java @@ -16,6 +16,7 @@ import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; @@ -31,19 +32,26 @@ import java.security.spec.ECPublicKeySpec; import java.security.spec.EllipticCurve; import java.security.spec.InvalidKeySpecException; +import java.security.spec.InvalidParameterSpecException; import java.util.Collections; import java.util.Enumeration; import java.util.List; import javax.crypto.KeyAgreement; import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1OutputStream; -import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERSequence; -import org.bouncycastle.crypto.params.ECNamedDomainParameters; +import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util; import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.ECPointUtil; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; @@ -110,10 +118,14 @@ static RubyClass _EC(final Ruby runtime) { return _PKey(runtime).getClass("EC"); } - public static RaiseException newECError(Ruby runtime, String message) { + private static RaiseException newECError(Ruby runtime, String message) { return Utils.newError(runtime, _PKey(runtime).getClass("ECError"), message); } + private static RaiseException newECError(Ruby runtime, String message, Exception cause) { + return Utils.newError(runtime, _PKey(runtime).getClass("ECError"), message, cause); + } + @JRubyMethod(meta = true) public static RubyArray builtin_curves(ThreadContext context, IRubyObject self) { final Ruby runtime = context.runtime; @@ -396,7 +408,6 @@ public IRubyObject check_key(final ThreadContext context) { @JRubyMethod(name = "generate_key") public PKeyEC generate_key(final ThreadContext context) { - // final ECDomainParameters params = getDomainParameters(); try { ECGenParameterSpec genSpec = new ECGenParameterSpec(getCurveName()); KeyPairGenerator gen = SecurityHelper.getKeyPairGenerator("EC"); // "BC" @@ -426,54 +437,69 @@ public static IRubyObject generate(final ThreadContext context, final IRubyObjec @JRubyMethod(name = "dsa_sign_asn1") public IRubyObject dsa_sign_asn1(final ThreadContext context, final IRubyObject data) { + if (privateKey == null) { + throw newECError(context.runtime, "Private EC key needed!"); + } try { - ECNamedCurveParameterSpec params = getParameterSpec(); - ASN1ObjectIdentifier oid = getCurveOID(getCurveName()); - ECNamedDomainParameters domainParams = new ECNamedDomainParameters(oid, - params.getCurve(), params.getG(), params.getN(), params.getH(), params.getSeed() - ); + final ECNamedCurveParameterSpec params = getParameterSpec(); final ECDSASigner signer = new ECDSASigner(); - final ECPrivateKey privKey = (ECPrivateKey) this.privateKey; - signer.init(true, new ECPrivateKeyParameters(privKey.getS(), domainParams)); + signer.init(true, new ECPrivateKeyParameters( + ((ECPrivateKey) this.privateKey).getS(), + new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()) + )); - final byte[] message = data.convertToString().getBytes(); - BigInteger[] signature = signer.generateSignature(message); // [r, s] - -// final byte[] r = signature[0].toByteArray(); -// final byte[] s = signature[1].toByteArray(); -// // ASN.1 encode as: 0x30 len 0x02 rlen (r) 0x02 slen (s) -// final int len = 1 + (1 + r.length) + 1 + (1 + s.length); -// -// final byte[] encoded = new byte[1 + 1 + len]; int i; -// encoded[0] = 0x30; -// encoded[1] = (byte) len; -// encoded[2] = 0x20; -// encoded[3] = (byte) r.length; -// System.arraycopy(r, 0, encoded, i = 4, r.length); i += r.length; -// encoded[i++] = 0x20; -// encoded[i++] = (byte) s.length; -// System.arraycopy(s, 0, encoded, i, s.length); + BigInteger[] signature = signer.generateSignature(data.convertToString().getBytes()); // [r, s] ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - ASN1OutputStream asn1 = ASN1OutputStream.create(bytes); + ASN1OutputStream asn1 = ASN1OutputStream.create(bytes, ASN1Encoding.DER); - ASN1EncodableVector v = new ASN1EncodableVector(); + ASN1EncodableVector v = new ASN1EncodableVector(2); v.add(new ASN1Integer(signature[0])); // r v.add(new ASN1Integer(signature[1])); // s - asn1.writeObject(new DLSequence(v)); + asn1.writeObject(new DERSequence(v)); + asn1.close(); return StringHelper.newString(context.runtime, bytes.buffer(), bytes.size()); } catch (IOException ex) { throw newECError(context.runtime, ex.getMessage()); } - catch (RuntimeException ex) { - throw (RaiseException) newECError(context.runtime, ex.toString()).initCause(ex); + catch (Exception ex) { + throw newECError(context.runtime, ex.toString(), ex); } } + @JRubyMethod(name = "dsa_verify_asn1") + public IRubyObject dsa_verify_asn1(final ThreadContext context, final IRubyObject data, final IRubyObject sign) { + final Ruby runtime = context.runtime; + try { + final ECNamedCurveParameterSpec params = getParameterSpec(); + + final ECDSASigner signer = new ECDSASigner(); + signer.init(false, new ECPublicKeyParameters( + EC5Util.convertPoint(publicKey.getParams(), publicKey.getW()), + new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()) + )); + + ASN1Primitive vec = new ASN1InputStream(sign.convertToString().getBytes()).readObject(); + + if (!(vec instanceof ASN1Sequence)) { + throw newECError(runtime, "invalid signature (not a sequence)"); + } + + ASN1Sequence seq = (ASN1Sequence) vec; + ASN1Integer r = ASN1Integer.getInstance(seq.getObjectAt(0)); + ASN1Integer s = ASN1Integer.getInstance(seq.getObjectAt(1)); + + boolean verify = signer.verifySignature(data.convertToString().getBytes(), r.getPositiveValue(), s.getPositiveValue()); + return runtime.newBoolean(verify); + } + catch (IOException|IllegalArgumentException|IllegalStateException ex) { + throw newECError(runtime, "invalid signature: " + ex.getMessage(), ex); + } + } @JRubyMethod(name = "dh_compute_key") public IRubyObject dh_compute_key(final ThreadContext context, final IRubyObject point) { diff --git a/src/test/ruby/ec/test_ec.rb b/src/test/ruby/ec/test_ec.rb index bb1b81cc..0cd685e2 100644 --- a/src/test/ruby/ec/test_ec.rb +++ b/src/test/ruby/ec/test_ec.rb @@ -237,15 +237,18 @@ def test_check_key end def test_sign_verify - key_file = File.join(File.dirname(__FILE__), 'private_key.pem') - - key = OpenSSL::PKey::EC.new(File.read(key_file)) - data = 'abcd' - digest = OpenSSL::Digest::SHA256.new - sig = key.sign(digest, data) - assert_true key.verify(digest, sig, data) - - key.sign(OpenSSL::Digest::SHA1.new, data) + p256 = Fixtures.pkey("p256.pem") + data = "Sign me!" + signature = p256.sign("SHA1", data) + assert_equal true, p256.verify("SHA1", signature, data) + + signature0 = (<<~'end;').unpack("m")[0] + MEQCIEOTY/hD7eI8a0qlzxkIt8LLZ8uwiaSfVbjX2dPAvN11AiAQdCYx56Fq + QdBp1B4sxJoA8jvODMMklMyBKVmudboA6A== + end; + assert_equal true, p256.verify("SHA256", signature0, data) + signature1 = signature0.succ + assert_equal false, p256.verify("SHA256", signature1, data) end def test_group_encoding @@ -293,13 +296,31 @@ def test_set_keys end end if false # NOT-IMPLEMENTED TODO - def test_dsa_sign_verify - data1 = 'foo' + def test_dsa_sign_verify_all + data1 = 'hashed-value' for key in @keys + next if key.group.curve_name == 'SM2' + sig = key.dsa_sign_asn1(data1) - assert(key.dsa_verify_asn1(data1, sig)) + assert_equal(true, key.dsa_verify_asn1(data1, sig)) + assert_equal(false, key.dsa_verify_asn1(data1 + 'X', sig)) end - end if false # NOT-IMPLEMENTED + end + + def test_sign_verify_raw + key = Fixtures.pkey("p256.pem") + data1 = "foo" + data2 = "bar" + + malformed_sig = "*" * 30 + + # Sign by #dsa_sign_asn1 + sig = key.dsa_sign_asn1(data1) + + assert_equal true, key.dsa_verify_asn1(data1, sig) + assert_equal false, key.dsa_verify_asn1(data2, sig) + assert_raise(OpenSSL::PKey::ECError) { key.dsa_verify_asn1(data1, malformed_sig) } + end # def test_dh_compute_key # for key in @keys