/* * Copyright (c) 2016 Karol Bucek. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.jruby.ext.openssl; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; import java.security.spec.EllipticCurve; import java.security.spec.InvalidKeySpecException; 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.ASN1Integer; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1OutputStream; import org.bouncycastle.asn1.DLSequence; import org.bouncycastle.crypto.params.ECNamedDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.signers.ECDSASigner; import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.ECPointUtil; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.jce.spec.ECNamedCurveSpec; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyBoolean; import org.jruby.RubyClass; import org.jruby.RubyModule; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Arity; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.component.VariableEntry; import org.jruby.ext.openssl.impl.CipherSpec; import static org.jruby.ext.openssl.OpenSSL.debug; import static org.jruby.ext.openssl.OpenSSL.debugStackTrace; import static org.jruby.ext.openssl.PKey._PKey; import org.jruby.ext.openssl.impl.ECPrivateKeyWithName; import static org.jruby.ext.openssl.impl.PKey.readECPrivateKey; import org.jruby.ext.openssl.util.ByteArrayOutputStream; import org.jruby.ext.openssl.x509store.PEMInputOutput; /** * OpenSSL::PKey::EC implementation. * * @author kares */ public final class PKeyEC extends PKey { private static final long serialVersionUID = 1L; private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { public PKeyEC allocate(Ruby runtime, RubyClass klass) { return new PKeyEC(runtime, klass); } }; public static void createPKeyEC(final Ruby runtime, final RubyModule PKey, final RubyClass PKeyPKey) { RubyClass EC = PKey.defineClassUnder("EC", PKeyPKey, ALLOCATOR); RubyClass PKeyError = PKey.getClass("PKeyError"); PKey.defineClassUnder("ECError", PKeyError, PKeyError.getAllocator()); EC.defineAnnotatedMethods(PKeyEC.class); EC.setConstant("NAMED_CURVE", runtime.newFixnum(1)); Point.createPoint(runtime, EC); Group.createGroup(runtime, EC); } static RubyClass _EC(final Ruby runtime) { return _PKey(runtime).getClass("EC"); } public static RaiseException newECError(Ruby runtime, String message) { return Utils.newError(runtime, _PKey(runtime).getClass("ECError"), message); } @JRubyMethod(meta = true) public static RubyArray builtin_curves(ThreadContext context, IRubyObject self) { final Ruby runtime = context.runtime; final RubyArray curves = runtime.newArray(); Enumeration names; names = org.bouncycastle.asn1.x9.X962NamedCurves.getNames(); while ( names.hasMoreElements() ) { final String name = (String) names.nextElement(); RubyString desc; if ( name.startsWith("prime") ) { desc = RubyString.newString(runtime, "X9.62 curve over a xxx bit prime field"); } else { desc = RubyString.newString(runtime, "X9.62 curve over a xxx bit binary field"); } curves.append(RubyArray.newArrayNoCopy(runtime, new IRubyObject[] { RubyString.newString(runtime, name), desc })); } names = org.bouncycastle.asn1.sec.SECNamedCurves.getNames(); while ( names.hasMoreElements() ) { RubyString name = RubyString.newString(runtime, (String) names.nextElement()); RubyString desc = RubyString.newString(runtime, "SECG curve over a xxx bit binary field"); curves.append(RubyArray.newArrayNoCopy(runtime, new IRubyObject[] { name, desc })); } names = org.bouncycastle.asn1.nist.NISTNamedCurves.getNames(); while ( names.hasMoreElements() ) { RubyString name = RubyString.newString(runtime, (String) names.nextElement()); IRubyObject[] nameAndDesc = new IRubyObject[] { name, RubyString.newEmptyString(runtime) }; curves.append(RubyArray.newArrayNoCopy(runtime, nameAndDesc)); } names = org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves.getNames(); while ( names.hasMoreElements() ) { RubyString name = RubyString.newString(runtime, (String) names.nextElement()); RubyString desc = RubyString.newString(runtime, "RFC 5639 curve over a xxx bit prime field"); curves.append(RubyArray.newArrayNoCopy(runtime, new IRubyObject[] { name, desc })); } return curves; } private static ASN1ObjectIdentifier getCurveOID(final String curveName) { ASN1ObjectIdentifier id; id = org.bouncycastle.asn1.sec.SECNamedCurves.getOID(curveName); if ( id != null ) return id; id = org.bouncycastle.asn1.x9.X962NamedCurves.getOID(curveName); if ( id != null ) return id; id = org.bouncycastle.asn1.nist.NISTNamedCurves.getOID(curveName); if ( id != null ) return id; id = org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves.getOID(curveName); if ( id != null ) return id; throw new IllegalStateException("could not identify curve name: " + curveName); } private static boolean isCurveName(final String curveName) { try { return getCurveOID(curveName) != null; } catch (IllegalStateException ex) { return false; } } private static String getCurveName(final ASN1ObjectIdentifier oid) { String name; name = org.bouncycastle.asn1.sec.SECNamedCurves.getName(oid); if ( name != null ) return name; name = org.bouncycastle.asn1.x9.X962NamedCurves.getName(oid); if ( name != null ) return name; name = org.bouncycastle.asn1.nist.NISTNamedCurves.getName(oid); if ( name != null ) return name; name = org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves.getName(oid); if ( name != null ) return name; throw new IllegalStateException("could not identify curve name from: " + oid); } public PKeyEC(Ruby runtime, RubyClass type) { super(runtime, type); } PKeyEC(Ruby runtime, PublicKey pubKey) { this(runtime, _EC(runtime), null, pubKey); } PKeyEC(Ruby runtime, RubyClass type, PrivateKey privKey, PublicKey pubKey) { super(runtime, type); this.privateKey = privKey; this.publicKey = (ECPublicKey) pubKey; } private transient Group group; private ECPublicKey publicKey; private transient PrivateKey privateKey; private String curveName; private String getCurveName() { return curveName; } // private ECNamedCurveParameterSpec getParameterSpec() { // return ECNamedCurveTable.getParameterSpec( getCurveName() ); // } @Override public PublicKey getPublicKey() { return publicKey; } @Override public PrivateKey getPrivateKey() { return privateKey; } @Override public String getAlgorithm() { return "EC"; } @JRubyMethod(rest = true, visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { final Ruby runtime = context.runtime; privateKey = null; publicKey = null; if ( Arity.checkArgumentCount(runtime, args, 0, 2) == 0 ) { return this; } IRubyObject arg = args[0]; if ( arg instanceof Group ) { this.group = (Group) arg; this.curveName = this.group.getCurveName(); return this; } IRubyObject pass = null; if ( args.length > 1 ) pass = args[1]; final char[] passwd = password(pass); final RubyString str = readInitArg(context, arg); final String strJava = str.toString(); if ( isCurveName(strJava) ) { this.curveName = strJava; return this; } Object key = null; final KeyFactory ecdsaFactory; try { ecdsaFactory = SecurityHelper.getKeyFactory("ECDSA"); } catch (NoSuchAlgorithmException e) { throw runtime.newRuntimeError("unsupported key algorithm (ECDSA)"); } catch (RuntimeException e) { throw runtime.newRuntimeError("unsupported key algorithm (ECDSA) " + e); } // TODO: ugly NoClassDefFoundError catching for no BC env. How can we remove this? boolean noClassDef = false; if ( key == null && ! noClassDef ) { // PEM_read_bio_DSAPrivateKey try { key = readPrivateKey(strJava, passwd); } catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); } catch (PEMInputOutput.PasswordRequiredException retry) { if ( ttySTDIN(context) ) { try { key = readPrivateKey(str, passwordPrompt(context)); } catch (Exception e) { debugStackTrace(runtime, e); } } } catch (Exception e) { debugStackTrace(runtime, e); } } if ( key == null && ! noClassDef ) { try { key = PEMInputOutput.readECPublicKey(new StringReader(strJava), passwd); } catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); } catch (Exception e) { debugStackTrace(runtime, e); } } if ( key == null && ! noClassDef ) { try { key = PEMInputOutput.readECPubKey(new StringReader(strJava)); } catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); } catch (Exception e) { debugStackTrace(runtime, e); } } if ( key == null && ! noClassDef ) { try { key = readECPrivateKey(ecdsaFactory, str.getBytes()); } catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); } catch (InvalidKeySpecException e) { debug(runtime, "PKeyEC could not read private key", e); } catch (IOException e) { debug(runtime, "PKeyEC could not read private key", e); } catch (RuntimeException e) { if ( isKeyGenerationFailure(e) ) debug(runtime, "PKeyEC could not read private key", e); else debugStackTrace(runtime, e); } } // if ( key == null && ! noClassDef ) { // try { // readECParameters // ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(str.getBytes()); // ECNamedCurveParameterSpec paramSpec = ECNamedCurveTable.getParameterSpec(oid.getId()); // // // ecdsaFactory.generatePublic(keySpec) // // } // catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); } // catch (InvalidKeySpecException e) { debug(runtime, "PKeyEC could not read public key", e); } // catch (IOException e) { debug(runtime, "PKeyEC could not read public key", e); } // catch (RuntimeException e) { // if ( isKeyGenerationFailure(e) ) debug(runtime, "PKeyEC could not read public key", e); // else debugStackTrace(runtime, e); // } // } if ( key == null ) key = tryPKCS8EncodedKey(runtime, ecdsaFactory, str.getBytes()); if ( key == null ) key = tryX509EncodedKey(runtime, ecdsaFactory, str.getBytes()); if ( key == null ) throw newECError(runtime, "Neither PUB key nor PRIV key:"); if ( key instanceof KeyPair ) { final PublicKey pubKey = ((KeyPair) key).getPublic(); final PrivateKey privKey = ((KeyPair) key).getPrivate(); if ( ! ( privKey instanceof ECPrivateKey ) ) { if ( privKey == null ) { throw newECError(runtime, "Neither PUB key nor PRIV key: (private key is null)"); } throw newECError(runtime, "Neither PUB key nor PRIV key: (invalid key type " + privKey.getClass().getName() + ")"); } this.publicKey = (ECPublicKey) pubKey; this.privateKey = (ECPrivateKey) privKey; unwrapPrivateKeyWithName(); } else if ( key instanceof ECPrivateKey ) { this.privateKey = (ECPrivateKey) key; unwrapPrivateKeyWithName(); } else if ( key instanceof ECPublicKey ) { this.publicKey = (ECPublicKey) key; this.privateKey = null; } else { throw newECError(runtime, "Neither PUB key nor PRIV key: " + key.getClass().getName()); } if ( publicKey != null ) { publicKey.getParams().getCurve(); } // TODO set curveName ?!?!?!?!?!?!?! return this; } private void unwrapPrivateKeyWithName() { final ECPrivateKey privKey = (ECPrivateKey) this.privateKey; if ( privKey instanceof ECPrivateKeyWithName ) { this.privateKey = ((ECPrivateKeyWithName) privKey).unwrap(); this.curveName = getCurveName( ((ECPrivateKeyWithName) privKey).getCurveNameOID() ); } } //private static ECNamedCurveParameterSpec readECParameters(final byte[] input) throws IOException { // ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(input); // return ECNamedCurveTable.getParameterSpec(oid.getId()); //} @JRubyMethod public IRubyObject check_key(final ThreadContext context) { return context.runtime.getTrue(); // TODO not implemented stub } @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("ECDSA"); // "BC" gen.initialize(genSpec, new SecureRandom()); KeyPair pair = gen.generateKeyPair(); this.publicKey = (ECPublicKey) pair.getPublic(); this.privateKey = pair.getPrivate(); } catch (GeneralSecurityException ex) { throw newECError(context.runtime, ex.toString()); } return this; } @JRubyMethod(name = "dsa_sign_asn1") public IRubyObject dsa_sign_asn1(final ThreadContext context, final IRubyObject data) { try { ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec(getCurveName()); ASN1ObjectIdentifier oid = getCurveOID(getCurveName()); ECNamedDomainParameters domainParams = new ECNamedDomainParameters(oid, params.getCurve(), params.getG(), params.getN(), params.getH(), params.getSeed() ); final ECDSASigner signer = new ECDSASigner(); final ECPrivateKey privKey = (ECPrivateKey) this.privateKey; signer.init(true, new ECPrivateKeyParameters(privKey.getS(), domainParams)); 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); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); ASN1OutputStream asn1 = new ASN1OutputStream(bytes); ASN1EncodableVector v = new ASN1EncodableVector(); v.add(new ASN1Integer(signature[0])); // r v.add(new ASN1Integer(signature[1])); // s asn1.writeObject(new DLSequence(v)); return StringHelper.newString(context.runtime, bytes.buffer(), bytes.size()); } catch (IOException ex) { throw newECError(context.runtime, ex.toString()); } catch (RuntimeException ex) { throw newECError(context.runtime, ex.toString()); } } @JRubyMethod(name = "dh_compute_key") public IRubyObject dh_compute_key(final ThreadContext context, final IRubyObject point) { try { KeyAgreement agreement = SecurityHelper.getKeyAgreement("ECDH"); // "BC" agreement.init(getPrivateKey()); if ( point.isNil() ) { agreement.doPhase(getPublicKey(), true); } else { final ECPoint ecPoint = ((Point) point).asECPoint(); final String name = getCurveName(); KeyFactory keyFactory = KeyFactory.getInstance("EC"); // "BC" ECParameterSpec spec = getParamSpec(name); ECPublicKey ecPublicKey = (ECPublicKey) keyFactory.generatePublic(new ECPublicKeySpec(ecPoint, spec)); agreement.doPhase(ecPublicKey, true); } final byte[] secret = agreement.generateSecret(); return StringHelper.newString(context.runtime, secret); } catch (NoSuchAlgorithmException ex) { throw newECError(context.runtime, ex.toString()); } catch (InvalidKeyException ex) { throw newECError(context.runtime, ex.toString()); } catch (GeneralSecurityException ex) { throw newECError(context.runtime, ex.toString()); } } private Group getGroup(boolean required) { if (group == null) { if (publicKey != null) { return group = new Group(getRuntime(), this); } if (required) throw new IllegalStateException("no group (without public key)"); } return group; } /** * @return OpenSSL::PKey::EC::Group */ @JRubyMethod public IRubyObject group() { final Group group = getGroup(false); return group == null ? getRuntime().getNil() : group; } @JRubyMethod(name = "group=") public IRubyObject set_group(IRubyObject group) { this.group = group.isNil() ? null : (Group) group; return group; } /** * @return OpenSSL::PKey::EC::Point */ @JRubyMethod public IRubyObject public_key(final ThreadContext context) { if ( publicKey == null ) return context.nil; return new Point(context.runtime, publicKey, getGroup(true)); } @JRubyMethod(name = "public_key=") public IRubyObject set_public_key(final ThreadContext context, final IRubyObject arg) { if ( ! ( arg instanceof Point ) ) { throw context.runtime.newTypeError(arg, _EC(context.runtime).getClass("Point")); } final Point point = (Point) arg; ECPublicKeySpec keySpec = new ECPublicKeySpec(point.asECPoint(), getParamSpec()); try { this.publicKey = (ECPublicKey) SecurityHelper.getKeyFactory("ECDSA").generatePublic(keySpec); return arg; } catch (GeneralSecurityException ex) { throw newECError(context.runtime, ex.getMessage()); } } private static ECParameterSpec getParamSpec(final String curveName) { ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveName); return new ECNamedCurveSpec(spec.getName(), spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed()); } private ECParameterSpec getParamSpec() { return getParamSpec(getCurveName()); } /** * @return OpenSSL::BN */ @JRubyMethod public IRubyObject private_key(final ThreadContext context) { if ( privateKey == null ) return context.nil; return BN.newBN(context.runtime, ((ECPrivateKey) privateKey).getS()); } @JRubyMethod(name = "private_key=") public IRubyObject set_private_key(final ThreadContext context, final IRubyObject arg) { final BigInteger s; if ( arg instanceof BN ) { s = ((BN) (arg)).getValue(); } else { s = (BigInteger) arg; } ECPrivateKeySpec keySpec = new ECPrivateKeySpec(s, getParamSpec()); try { this.privateKey = SecurityHelper.getKeyFactory("ECDSA").generatePrivate(keySpec); return arg; } catch (GeneralSecurityException ex) { throw newECError(context.runtime, ex.getMessage()); } } @JRubyMethod(name = "public_key?") public RubyBoolean public_p() { return publicKey != null ? getRuntime().getTrue() : getRuntime().getFalse(); } @JRubyMethod(name = "private_key?") public RubyBoolean private_p() { return privateKey != null ? getRuntime().getTrue() : getRuntime().getFalse(); } @Override @JRubyMethod(name = "to_der") public RubyString to_der() { final byte[] bytes; try { bytes = toDER(); } catch (IOException e) { throw newECError(getRuntime(), e.getMessage()); } return StringHelper.newString(getRuntime(), bytes); } private byte[] toDER() throws IOException { if ( publicKey != null && privateKey == null ) { return publicKey.getEncoded(); } if ( privateKey == null ) { throw new IllegalStateException("private key as well as public key are null"); } return privateKey.getEncoded(); } @Override @JRubyMethod(name = "to_pem", alias = "export", rest = true) public RubyString to_pem(final IRubyObject[] args) { Arity.checkArgumentCount(getRuntime(), args, 0, 2); CipherSpec spec = null; char[] passwd = null; if ( args.length > 0 ) { spec = cipherSpec( args[0] ); if ( args.length > 1 ) passwd = password( args[1] ); } try { final StringWriter writer = new StringWriter(); if ( privateKey != null ) { PEMInputOutput.writeECPrivateKey(writer, (ECPrivateKey) privateKey, spec, passwd); } else { PEMInputOutput.writeECPublicKey(writer, publicKey); } return RubyString.newString(getRuntime(), writer.getBuffer()); } catch (IOException ex) { throw newECError(getRuntime(), ex.getMessage()); } } @JRubyClass(name = "OpenSSL::PKey::EC::Group") public static final class Group extends RubyObject { private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { public Group allocate(Ruby runtime, RubyClass klass) { return new Group(runtime, klass); } }; static void createGroup(final Ruby runtime, final RubyClass EC) { RubyClass Group = EC.defineClassUnder("Group", runtime.getObject(), ALLOCATOR); // OpenSSL::PKey::EC::Group::Error RubyClass OpenSSLError = OpenSSL._OpenSSLError(runtime); Group.defineClassUnder("Error", OpenSSLError, OpenSSLError.getAllocator()); Group.defineAnnotatedMethods(Group.class); } private transient PKeyEC key; private ECParameterSpec paramSpec; private RubyString curve_name; public Group(Ruby runtime, RubyClass type) { super(runtime, type); } Group(Ruby runtime, PKeyEC key) { this(runtime, _EC(runtime).getClass("Group")); this.key = key; this.paramSpec = key.publicKey.getParams(); } private String getCurveName() { if (key != null) return key.getCurveName(); return curve_name.toString(); } @JRubyMethod(rest = true, visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { final Ruby runtime = context.runtime; if ( Arity.checkArgumentCount(runtime, args, 1, 4) == 1 ) { IRubyObject arg = args[0]; if ( arg instanceof Group ) { IRubyObject curve_name = ((Group) arg).curve_name(context); this.curve_name = curve_name.isNil() ? null : (RubyString) curve_name; return this; } this.curve_name = ((RubyString) arg); // TODO PEM/DER parsing not implemented } return this; } @Override @JRubyMethod(name = { "==", "eql?" }) public IRubyObject op_equal(final ThreadContext context, final IRubyObject obj) { if ( paramSpec == null ) return context.nil; if ( obj instanceof Group ) { final Group that = (Group) obj; boolean equals = this.paramSpec.equals(that.paramSpec); return context.runtime.newBoolean(equals); } return context.runtime.getFalse(); } @JRubyMethod public IRubyObject curve_name(final ThreadContext context) { if (curve_name == null) { String prefix, curveName = key.getCurveName(); // BC 1.54: "brainpoolP512t1" 1.55: "brainpoolp512t1" if (curveName.startsWith(prefix = "brainpoolp")) { curveName = "brainpoolP" + curveName.substring(prefix.length()); } curve_name = RubyString.newString(context.runtime, curveName); } return curve_name.dup(); } @JRubyMethod public IRubyObject order(final ThreadContext context) { if ( paramSpec == null ) return context.nil; return BN.newBN(context.runtime, paramSpec.getOrder()); } @JRubyMethod public IRubyObject cofactor(final ThreadContext context) { if ( paramSpec == null ) return context.nil; return context.runtime.newFixnum(paramSpec.getCofactor()); } @JRubyMethod public IRubyObject seed(final ThreadContext context) { if ( paramSpec == null ) return context.nil; final byte[] seed = paramSpec.getCurve().getSeed(); return seed == null ? context.nil : StringHelper.newString(context.runtime, seed); } @JRubyMethod public IRubyObject degree(final ThreadContext context) { if ( paramSpec == null ) return context.nil; final int fieldSize = paramSpec.getCurve().getField().getFieldSize(); return context.runtime.newFixnum(fieldSize); } @JRubyMethod public IRubyObject generator(final ThreadContext context) { if ( paramSpec == null ) return context.nil; final ECPoint generator = paramSpec.getGenerator(); //final int bitLength = paramSpec.getOrder().bitLength(); return new Point(context.runtime, generator, this); } @JRubyMethod(name = { "to_pem" }, alias = "export", rest = true) public RubyString to_pem(final ThreadContext context, final IRubyObject[] args) { Arity.checkArgumentCount(context.runtime, args, 0, 2); CipherSpec spec = null; char[] passwd = null; if ( args.length > 0 ) { spec = cipherSpec( args[0] ); if ( args.length > 1 ) passwd = password(args[1]); } try { final StringWriter writer = new StringWriter(); PEMInputOutput.writeECParameters(writer, getCurveOID(getCurveName()), spec, passwd); return RubyString.newString(context.runtime, writer.getBuffer()); } catch (IOException ex) { throw newECError(context.runtime, ex.getMessage()); } } final EllipticCurve getCurve() { if (paramSpec == null) { paramSpec = getParamSpec(getCurveName()); } return paramSpec.getCurve(); } // @Override // @JRubyMethod // @SuppressWarnings("unchecked") // public IRubyObject inspect() { // final EllipticCurve curve = getCurve(); // final StringBuilder part = new StringBuilder(); // String cname = getMetaClass().getRealClass().getName(); // part.append("#<").append(cname).append(":0x"); // part.append(Integer.toHexString(System.identityHashCode(this))); // // part.append(' '); // part.append(" a:").append(curve.getA()).append(" b:").append(curve.getA()); // return RubyString.newString(getRuntime(), part.append('>')); // } } @JRubyClass(name = "OpenSSL::PKey::EC::Point") public static final class Point extends RubyObject { private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { public Point allocate(Ruby runtime, RubyClass klass) { return new Point(runtime, klass); } }; static void createPoint(final Ruby runtime, final RubyClass EC) { RubyClass Point = EC.defineClassUnder("Point", runtime.getObject(), ALLOCATOR); // OpenSSL::PKey::EC::Point::Error RubyClass OpenSSLError = OpenSSL._OpenSSLError(runtime); Point.defineClassUnder("Error", OpenSSLError, OpenSSLError.getAllocator()); Point.defineAnnotatedMethods(Point.class); } public Point(Ruby runtime, RubyClass type) { super(runtime, type); } // private transient ECPublicKey publicKey; private ECPoint point; //private int bitLength; private Group group; Point(Ruby runtime, ECPublicKey publicKey, Group group) { this(runtime, _EC(runtime).getClass("Point")); //this.publicKey = publicKey; this.point = publicKey.getW(); this.group = group; } Point(Ruby runtime, ECPoint point, Group group) { this(runtime, _EC(runtime).getClass("Point")); this.point = point; this.group = group; } private static RaiseException newError(final Ruby runtime, final String message) { final RubyClass Error = _EC(runtime).getClass("Point").getClass("Error"); return Utils.newError(runtime, Error, message); } @JRubyMethod(rest = true, visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { final Ruby runtime = context.runtime; final int argc = Arity.checkArgumentCount(runtime, args, 1, 2); final IRubyObject arg = args[0]; if ( arg instanceof Point ) { this.group = ((Point) arg).group; this.point = ((Point) arg).point; return this; } if ( arg instanceof Group ) { this.group = (Group) arg; } if ( argc == 2 ) { // (group, bn) final byte[] encoded = ((BN) args[1]).getValue().abs().toByteArray(); try { this.point = ECPointUtil.decodePoint(group.getCurve(), encoded); } catch (IllegalArgumentException ex) { // MRI: OpenSSL::PKey::EC::Point::Error: invalid encoding throw newError(context.runtime, ex.getMessage()); } } return this; } @Override @JRubyMethod(name = { "==", "eql?" }) public IRubyObject op_equal(final ThreadContext context, final IRubyObject obj) { if (obj instanceof Point) { final Point that = (Point) obj; boolean equals = this.point.equals(that.point); return context.runtime.newBoolean(equals); } return context.runtime.getFalse(); } /** * @return OpenSSL::PKey::EC::Group */ @JRubyMethod public IRubyObject group() { return group == null ? getRuntime().getNil() : group; } private ECPoint asECPoint() { return point; // return publicKey.getW(); } private int bitLength() { return group.paramSpec.getOrder().bitLength(); } @JRubyMethod public BN to_bn(final ThreadContext context) { final byte[] encoded = encode(bitLength(), point); return BN.newBN(context.runtime, new BigInteger(1, encoded)); } private boolean isInfinity() { return point == ECPoint.POINT_INFINITY; } @JRubyMethod(name = "infinity?") public RubyBoolean infinity_p() { return getRuntime().newBoolean( isInfinity() ); } @JRubyMethod(name = "set_to_infinity!") public IRubyObject set_to_infinity_b() { this.point = ECPoint.POINT_INFINITY; return this; } @Override @JRubyMethod @SuppressWarnings("unchecked") public IRubyObject inspect() { VariableEntry entry = new VariableEntry( "group", group == null ? (Object) "nil" : group ); return ObjectSupport.inspect(this, (List) Collections.singletonList(entry)); } } static byte[] encode(final ECPublicKey pubKey) { return encode(pubKey.getParams().getOrder().bitLength(), pubKey.getW()); } private static byte[] encode(final int bitLength, final ECPoint point) { if ( point == ECPoint.POINT_INFINITY ) return new byte[1]; final int bytesLength = (bitLength + 7) / 8; byte[] encoded = new byte[1 + bytesLength + bytesLength]; encoded[0] = 0x04; addIntBytes(point.getAffineX(), bytesLength, encoded, 1); addIntBytes(point.getAffineY(), bytesLength, encoded, 1 + bytesLength); return encoded; } private static void addIntBytes(BigInteger i, final int length, final byte[] dest, final int destOffset) { final byte[] bytes = i.toByteArray(); if (length < bytes.length) { System.arraycopy(bytes, bytes.length - length, dest, destOffset, length); } else if (length > bytes.length) { System.arraycopy(bytes, 0, dest, destOffset + (length - bytes.length), bytes.length); } else { System.arraycopy(bytes, 0, dest, destOffset, length); } } }