Skip to content

Commit 3646eaa

Browse files
committed
working pieces of PKey::EC - DH encryption expected to behave correctly
1 parent ca28b7e commit 3646eaa

File tree

4 files changed

+368
-39
lines changed

4 files changed

+368
-39
lines changed

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

Lines changed: 174 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.io.StringWriter;
1313
import java.math.BigInteger;
1414
import java.security.GeneralSecurityException;
15+
import java.security.InvalidKeyException;
1516
import java.security.KeyFactory;
1617
import java.security.KeyPair;
1718
import java.security.KeyPairGenerator;
@@ -29,26 +30,23 @@
2930
import java.security.spec.ECPublicKeySpec;
3031
import java.security.spec.EllipticCurve;
3132
import java.security.spec.InvalidKeySpecException;
33+
import java.util.Collections;
3234
import java.util.Enumeration;
33-
import java.util.logging.Level;
34-
import java.util.logging.Logger;
35+
import java.util.List;
36+
import javax.crypto.KeyAgreement;
3537
import org.bouncycastle.asn1.ASN1EncodableVector;
3638
import org.bouncycastle.asn1.ASN1Integer;
3739
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
40+
import org.bouncycastle.asn1.ASN1OutputStream;
3841
import org.bouncycastle.asn1.DLSequence;
3942

40-
import org.bouncycastle.crypto.params.ECDomainParameters;
41-
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
42-
//import org.bouncycastle.jce.interfaces.ECPublicKey;
43-
//import org.bouncycastle.jce.interfaces.ECPrivateKey;
43+
import org.bouncycastle.crypto.params.ECNamedDomainParameters;
44+
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
45+
import org.bouncycastle.crypto.signers.ECDSASigner;
4446
import org.bouncycastle.jce.ECNamedCurveTable;
4547
import org.bouncycastle.jce.ECPointUtil;
4648
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
4749
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
48-
//import org.bouncycastle.jce.spec.ECParameterSpec;
49-
//import org.bouncycastle.jce.spec.ECPublicKeySpec;
50-
//import org.bouncycastle.jce.spec.ECPrivateKeySpec;
51-
//import org.bouncycastle.math.ec.ECPoint;
5250

5351
import org.jruby.Ruby;
5452
import org.jruby.RubyArray;
@@ -60,19 +58,21 @@
6058
import org.jruby.anno.JRubyClass;
6159
import org.jruby.anno.JRubyMethod;
6260
import org.jruby.exceptions.RaiseException;
63-
import org.jruby.ext.openssl.x509store.PEMInputOutput;
6461
import org.jruby.runtime.Arity;
6562
import org.jruby.runtime.ObjectAllocator;
6663
import org.jruby.runtime.ThreadContext;
67-
import org.jruby.runtime.builtin.IRubyObject;
6864
import org.jruby.runtime.Visibility;
65+
import org.jruby.runtime.builtin.IRubyObject;
66+
import org.jruby.runtime.component.VariableEntry;
6967

7068
import org.jruby.ext.openssl.impl.CipherSpec;
7169
import static org.jruby.ext.openssl.OpenSSL.debug;
7270
import static org.jruby.ext.openssl.OpenSSL.debugStackTrace;
7371
import static org.jruby.ext.openssl.PKey._PKey;
7472
import org.jruby.ext.openssl.impl.ECPrivateKeyWithName;
7573
import static org.jruby.ext.openssl.impl.PKey.readECPrivateKey;
74+
import org.jruby.ext.openssl.util.ByteArrayOutputStream;
75+
import org.jruby.ext.openssl.x509store.PEMInputOutput;
7676

7777
/**
7878
* OpenSSL::PKey::EC implementation.
@@ -81,8 +81,7 @@
8181
*/
8282
public final class PKeyEC extends PKey {
8383

84-
// TODO
85-
// private static final long serialVersionUID = -1L;
84+
private static final long serialVersionUID = 1L;
8685

8786
private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
8887
public PKeyEC allocate(Ruby runtime, RubyClass klass) { return new PKeyEC(runtime, klass); }
@@ -166,6 +165,13 @@ private static ASN1ObjectIdentifier getCurveOID(final String curveName) {
166165
throw new IllegalStateException("could not identify curve name: " + curveName);
167166
}
168167

168+
private static boolean isCurveName(final String curveName) {
169+
try {
170+
return getCurveOID(curveName) != null;
171+
}
172+
catch (IllegalStateException ex) { return false; }
173+
}
174+
169175
private static String getCurveName(final ASN1ObjectIdentifier oid) {
170176
String name;
171177
name = org.bouncycastle.asn1.sec.SECNamedCurves.getName(oid);
@@ -233,6 +239,12 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
233239
if ( args.length > 1 ) pass = args[1];
234240
final char[] passwd = password(pass);
235241
final RubyString str = readInitArg(context, arg);
242+
final String strJava = str.toString();
243+
244+
if ( isCurveName(strJava) ) {
245+
this.curveName = strJava;
246+
return this;
247+
}
236248

237249
Object key = null;
238250
final KeyFactory ecdsaFactory;
@@ -249,7 +261,7 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
249261
boolean noClassDef = false;
250262
if ( key == null && ! noClassDef ) { // PEM_read_bio_DSAPrivateKey
251263
try {
252-
key = readPrivateKey(str, passwd);
264+
key = readPrivateKey(strJava, passwd);
253265
}
254266
catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); }
255267
catch (PEMInputOutput.PasswordRequiredException retry) {
@@ -262,14 +274,14 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
262274
}
263275
if ( key == null && ! noClassDef ) {
264276
try {
265-
key = PEMInputOutput.readECPublicKey(new StringReader(str.toString()), passwd);
277+
key = PEMInputOutput.readECPublicKey(new StringReader(strJava), passwd);
266278
}
267279
catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); }
268280
catch (Exception e) { debugStackTrace(runtime, e); }
269281
}
270282
if ( key == null && ! noClassDef ) {
271283
try {
272-
key = PEMInputOutput.readECPubKey(new StringReader(str.toString()));
284+
key = PEMInputOutput.readECPubKey(new StringReader(strJava));
273285
}
274286
catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); }
275287
catch (Exception e) { debugStackTrace(runtime, e); }
@@ -377,19 +389,84 @@ public PKeyEC generate_key(final ThreadContext context) {
377389

378390
@JRubyMethod(name = "dsa_sign_asn1")
379391
public IRubyObject dsa_sign_asn1(final ThreadContext context, final IRubyObject data) {
380-
// final ECDomainParameters params = getDomainParameters();
381392
try {
382-
ECGenParameterSpec genSpec = new ECGenParameterSpec(getCurveName());
383-
KeyPairGenerator gen = SecurityHelper.getKeyPairGenerator("ECDSA"); // "BC"
384-
gen.initialize(genSpec, new SecureRandom());
385-
KeyPair pair = gen.generateKeyPair();
386-
this.publicKey = (ECPublicKey) pair.getPublic();
387-
this.privateKey = pair.getPrivate();
393+
ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec(getCurveName());
394+
ASN1ObjectIdentifier oid = getCurveOID(getCurveName());
395+
ECNamedDomainParameters domainParams = new ECNamedDomainParameters(oid,
396+
params.getCurve(), params.getG(), params.getN(), params.getH(), params.getSeed()
397+
);
398+
399+
final ECDSASigner signer = new ECDSASigner();
400+
final ECPrivateKey privKey = (ECPrivateKey) this.privateKey;
401+
signer.init(true, new ECPrivateKeyParameters(privKey.getS(), domainParams));
402+
403+
final byte[] message = data.convertToString().getBytes();
404+
BigInteger[] signature = signer.generateSignature(message); // [r, s]
405+
406+
// final byte[] r = signature[0].toByteArray();
407+
// final byte[] s = signature[1].toByteArray();
408+
// // ASN.1 encode as: 0x30 len 0x02 rlen (r) 0x02 slen (s)
409+
// final int len = 1 + (1 + r.length) + 1 + (1 + s.length);
410+
//
411+
// final byte[] encoded = new byte[1 + 1 + len]; int i;
412+
// encoded[0] = 0x30;
413+
// encoded[1] = (byte) len;
414+
// encoded[2] = 0x20;
415+
// encoded[3] = (byte) r.length;
416+
// System.arraycopy(r, 0, encoded, i = 4, r.length); i += r.length;
417+
// encoded[i++] = 0x20;
418+
// encoded[i++] = (byte) s.length;
419+
// System.arraycopy(s, 0, encoded, i, s.length);
420+
421+
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
422+
ASN1OutputStream asn1 = new ASN1OutputStream(bytes);
423+
424+
ASN1EncodableVector v = new ASN1EncodableVector();
425+
v.add(new ASN1Integer(signature[0])); // r
426+
v.add(new ASN1Integer(signature[1])); // s
427+
428+
asn1.writeObject(new DLSequence(v));
429+
430+
return StringHelper.newString(context.runtime, bytes.buffer(), bytes.size());
431+
}
432+
catch (IOException ex) {
433+
throw newECError(context.runtime, ex.toString());
434+
}
435+
catch (RuntimeException ex) {
436+
throw newECError(context.runtime, ex.toString());
437+
}
438+
}
439+
440+
441+
@JRubyMethod(name = "dh_compute_key")
442+
public IRubyObject dh_compute_key(final ThreadContext context, final IRubyObject point) {
443+
try {
444+
KeyAgreement agreement = SecurityHelper.getKeyAgreement("ECDH"); // "BC"
445+
agreement.init(getPrivateKey());
446+
if ( point.isNil() ) {
447+
agreement.doPhase(getPublicKey(), true);
448+
}
449+
else {
450+
final ECPoint ecPoint = ((Point) point).asECPoint();
451+
final String name = getCurveName();
452+
453+
KeyFactory keyFactory = KeyFactory.getInstance("EC"); // "BC"
454+
ECParameterSpec spec = getParamSpec(name);
455+
ECPublicKey ecPublicKey = (ECPublicKey) keyFactory.generatePublic(new ECPublicKeySpec(ecPoint, spec));
456+
agreement.doPhase(ecPublicKey, true);
457+
}
458+
final byte[] secret = agreement.generateSecret();
459+
return StringHelper.newString(context.runtime, secret);
460+
}
461+
catch (NoSuchAlgorithmException ex) {
462+
throw newECError(context.runtime, ex.toString());
463+
}
464+
catch (InvalidKeyException ex) {
465+
throw newECError(context.runtime, ex.toString());
388466
}
389467
catch (GeneralSecurityException ex) {
390468
throw newECError(context.runtime, ex.toString());
391469
}
392-
return this;
393470
}
394471

395472
private Group getGroup(boolean required) {
@@ -433,18 +510,25 @@ public IRubyObject set_public_key(final ThreadContext context, final IRubyObject
433510
throw context.runtime.newTypeError(arg, _EC(context.runtime).getClass("Point"));
434511
}
435512
final Point point = (Point) arg;
436-
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(getCurveName());
437-
ECParameterSpec params = new ECNamedCurveSpec(spec.getName(), spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
438-
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point.asECPoint(), params);
513+
ECPublicKeySpec keySpec = new ECPublicKeySpec(point.asECPoint(), getParamSpec());
439514
try {
440-
this.publicKey = (ECPublicKey) SecurityHelper.getKeyFactory("ECDSA").generatePublic(pubKeySpec);
515+
this.publicKey = (ECPublicKey) SecurityHelper.getKeyFactory("ECDSA").generatePublic(keySpec);
441516
return arg;
442517
}
443518
catch (GeneralSecurityException ex) {
444519
throw newECError(context.runtime, ex.getMessage());
445520
}
446521
}
447522

523+
private static ECParameterSpec getParamSpec(final String curveName) {
524+
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveName);
525+
return new ECNamedCurveSpec(spec.getName(), spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
526+
}
527+
528+
private ECParameterSpec getParamSpec() {
529+
return getParamSpec(getCurveName());
530+
}
531+
448532
/**
449533
* @return OpenSSL::BN
450534
*/
@@ -464,11 +548,9 @@ public IRubyObject set_private_key(final ThreadContext context, final IRubyObjec
464548
else {
465549
s = (BigInteger) arg;
466550
}
467-
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(getCurveName());
468-
ECParameterSpec params = new ECNamedCurveSpec(spec.getName(), spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
469-
ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(s, params);
551+
ECPrivateKeySpec keySpec = new ECPrivateKeySpec(s, getParamSpec());
470552
try {
471-
this.privateKey = SecurityHelper.getKeyFactory("ECDSA").generatePrivate(privKeySpec);
553+
this.privateKey = SecurityHelper.getKeyFactory("ECDSA").generatePrivate(keySpec);
472554
return arg;
473555
}
474556
catch (GeneralSecurityException ex) {
@@ -510,14 +592,14 @@ private byte[] toDER() throws IOException {
510592
}
511593

512594
@Override
513-
@JRubyMethod(name = { "to_pem" }, alias = "export", rest = true)
595+
@JRubyMethod(name = "to_pem", alias = "export", rest = true)
514596
public RubyString to_pem(final IRubyObject[] args) {
515597
Arity.checkArgumentCount(getRuntime(), args, 0, 2);
516598

517599
CipherSpec spec = null; char[] passwd = null;
518600
if ( args.length > 0 ) {
519601
spec = cipherSpec( args[0] );
520-
if ( args.length > 1 ) passwd = password(args[1]);
602+
if ( args.length > 1 ) passwd = password( args[1] );
521603
}
522604

523605
try {
@@ -641,7 +723,7 @@ public IRubyObject degree(final ThreadContext context) {
641723
public IRubyObject generator(final ThreadContext context) {
642724
if ( paramSpec == null ) return context.nil;
643725
final ECPoint generator = paramSpec.getGenerator();
644-
final int bitLength = paramSpec.getOrder().bitLength();
726+
//final int bitLength = paramSpec.getOrder().bitLength();
645727
return new Point(context.runtime, generator, this);
646728
}
647729

@@ -665,7 +747,26 @@ public RubyString to_pem(final ThreadContext context, final IRubyObject[] args)
665747
}
666748
}
667749

668-
final EllipticCurve getCurve() { return paramSpec.getCurve(); }
750+
final EllipticCurve getCurve() {
751+
if (paramSpec == null) {
752+
paramSpec = getParamSpec(getCurveName());
753+
}
754+
return paramSpec.getCurve();
755+
}
756+
757+
// @Override
758+
// @JRubyMethod
759+
// @SuppressWarnings("unchecked")
760+
// public IRubyObject inspect() {
761+
// final EllipticCurve curve = getCurve();
762+
// final StringBuilder part = new StringBuilder();
763+
// String cname = getMetaClass().getRealClass().getName();
764+
// part.append("#<").append(cname).append(":0x");
765+
// part.append(Integer.toHexString(System.identityHashCode(this)));
766+
// // part.append(' ');
767+
// part.append(" a:").append(curve.getA()).append(" b:").append(curve.getA());
768+
// return RubyString.newString(getRuntime(), part.append('>'));
769+
// }
669770

670771
}
671772

@@ -708,6 +809,11 @@ public Point(Ruby runtime, RubyClass type) {
708809
this.group = group;
709810
}
710811

812+
private static RaiseException newError(final Ruby runtime, final String message) {
813+
final RubyClass Error = _EC(runtime).getClass("Point").getClass("Error");
814+
return Utils.newError(runtime, Error, message);
815+
}
816+
711817
@JRubyMethod(rest = true, visibility = Visibility.PRIVATE)
712818
public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) {
713819
final Ruby runtime = context.runtime;
@@ -726,7 +832,13 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
726832
}
727833
if ( argc == 2 ) { // (group, bn)
728834
final byte[] encoded = ((BN) args[1]).getValue().abs().toByteArray();
729-
this.point = ECPointUtil.decodePoint(group.getCurve(), encoded);
835+
try {
836+
this.point = ECPointUtil.decodePoint(group.getCurve(), encoded);
837+
}
838+
catch (IllegalArgumentException ex) {
839+
// MRI: OpenSSL::PKey::EC::Point::Error: invalid encoding
840+
throw newError(context.runtime, ex.getMessage());
841+
}
730842
}
731843

732844
return this;
@@ -762,7 +874,30 @@ private int bitLength() {
762874
@JRubyMethod
763875
public BN to_bn(final ThreadContext context) {
764876
final byte[] encoded = encode(bitLength(), point);
765-
return BN.newBN(context.runtime, new BigInteger(encoded));
877+
return BN.newBN(context.runtime, new BigInteger(1, encoded));
878+
}
879+
880+
private boolean isInfinity() {
881+
return point == ECPoint.POINT_INFINITY;
882+
}
883+
884+
@JRubyMethod(name = "infinity?")
885+
public RubyBoolean infinity_p() {
886+
return getRuntime().newBoolean( isInfinity() );
887+
}
888+
889+
@JRubyMethod(name = "set_to_infinity!")
890+
public IRubyObject set_to_infinity_b() {
891+
this.point = ECPoint.POINT_INFINITY;
892+
return this;
893+
}
894+
895+
@Override
896+
@JRubyMethod
897+
@SuppressWarnings("unchecked")
898+
public IRubyObject inspect() {
899+
VariableEntry entry = new VariableEntry( "group", group == null ? (Object) "nil" : group );
900+
return ObjectSupport.inspect(this, (List) Collections.singletonList(entry));
766901
}
767902

768903
}

src/test/ruby/ec/private_key.pem

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MD4CAQEEDrtUuJOpUTwJpjf3LJUuoAcGBSuBBAAGoSADHgAEe2LZ/iq6+RafJRYv
3+
bkJPniq3aSf9nv1Xu+DMMg==
4+
-----END EC PRIVATE KEY-----

src/test/ruby/ec/private_key2.pem

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MIHaAgEBBEAJF2SrSI8nWVc9JR3qfvmwBpCmb0x5XUc8Tzc1KZ4DtJrg+5Ut6vQR
3+
QK7YIZifynst7q7DODVhgf/D16L8069GoAsGCSskAwMCCAEBDqGBhQOBggAEB/T1
4+
u6sxFny3OW83HXVFXaBUkJtkyByyb3HNuFXSshr3VAozUbHtB8avShcy2jBTULd3
5+
FOzTj5R/ME5egOG1fTMQRSxM85r/cSKFguiJkZGGWETwXvlJ7LRhy5GSeV2fgwLV
6+
TS/ljdy6ho/E+pfViDqIZa+FSTBhbB67TZlbJQw=
7+
-----END EC PRIVATE KEY-----

0 commit comments

Comments
 (0)