1212import java .io .StringWriter ;
1313import java .math .BigInteger ;
1414import java .security .GeneralSecurityException ;
15+ import java .security .InvalidKeyException ;
1516import java .security .KeyFactory ;
1617import java .security .KeyPair ;
1718import java .security .KeyPairGenerator ;
2930import java .security .spec .ECPublicKeySpec ;
3031import java .security .spec .EllipticCurve ;
3132import java .security .spec .InvalidKeySpecException ;
33+ import java .util .Collections ;
3234import 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 ;
3537import org .bouncycastle .asn1 .ASN1EncodableVector ;
3638import org .bouncycastle .asn1 .ASN1Integer ;
3739import org .bouncycastle .asn1 .ASN1ObjectIdentifier ;
40+ import org .bouncycastle .asn1 .ASN1OutputStream ;
3841import 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 ;
4446import org .bouncycastle .jce .ECNamedCurveTable ;
4547import org .bouncycastle .jce .ECPointUtil ;
4648import org .bouncycastle .jce .spec .ECNamedCurveParameterSpec ;
4749import 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
5351import org .jruby .Ruby ;
5452import org .jruby .RubyArray ;
6058import org .jruby .anno .JRubyClass ;
6159import org .jruby .anno .JRubyMethod ;
6260import org .jruby .exceptions .RaiseException ;
63- import org .jruby .ext .openssl .x509store .PEMInputOutput ;
6461import org .jruby .runtime .Arity ;
6562import org .jruby .runtime .ObjectAllocator ;
6663import org .jruby .runtime .ThreadContext ;
67- import org .jruby .runtime .builtin .IRubyObject ;
6864import org .jruby .runtime .Visibility ;
65+ import org .jruby .runtime .builtin .IRubyObject ;
66+ import org .jruby .runtime .component .VariableEntry ;
6967
7068import org .jruby .ext .openssl .impl .CipherSpec ;
7169import static org .jruby .ext .openssl .OpenSSL .debug ;
7270import static org .jruby .ext .openssl .OpenSSL .debugStackTrace ;
7371import static org .jruby .ext .openssl .PKey ._PKey ;
7472import org .jruby .ext .openssl .impl .ECPrivateKeyWithName ;
7573import 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.
8181 */
8282public 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 }
0 commit comments