Skip to content

Commit a56b251

Browse files
committed
[compat] implement EC::Group#to_der + related bits
1 parent add8620 commit a56b251

File tree

2 files changed

+123
-2
lines changed

2 files changed

+123
-2
lines changed

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

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,8 @@ static RaiseException newError(final Ruby runtime, final String message) {
860860

861861
private PointConversion conversionForm = PointConversion.UNCOMPRESSED;
862862

863+
private int asn1Flag = 1; // OPENSSL_EC_NAMED_CURVE
864+
863865
private String curveName;
864866
private RubyString impl_curve_name;
865867

@@ -881,11 +883,55 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
881883
IRubyObject arg = args[0];
882884

883885
if ( arg instanceof Group ) {
884-
this.curveName = ((Group) arg).curveName;
886+
final Group src = (Group) arg;
887+
this.curveName = src.curveName;
888+
this.impl_curve_name = src.impl_curve_name;
889+
this.paramSpec = src.paramSpec;
890+
this.asn1Flag = src.asn1Flag;
891+
this.conversionForm = src.conversionForm;
885892
return this;
886893
}
887894

888-
this.impl_curve_name = arg.convertToString();
895+
final RubyString strArg = arg.convertToString();
896+
final byte[] bytes = strArg.getBytes();
897+
// Detect DER input: OID tag (0x06) for named curve, SEQUENCE tag (0x30) for explicit params
898+
if (bytes.length > 0 && (bytes[0] == 0x06 || bytes[0] == 0x30)) {
899+
try {
900+
final ASN1Primitive primitive = ASN1Primitive.fromByteArray(bytes);
901+
if (primitive instanceof ASN1ObjectIdentifier) {
902+
// Named curve: DER-encoded OID -> look up curve name
903+
setCurveName(runtime, PKeyEC.getCurveName((ASN1ObjectIdentifier) primitive));
904+
this.asn1Flag = 1; // NAMED_CURVE
905+
return this;
906+
} else if (primitive instanceof ASN1Sequence) {
907+
// Explicit parameters: X9.62 ECParameters SEQUENCE
908+
final X9ECParameters ecParams = X9ECParameters.getInstance(primitive);
909+
final EllipticCurve curve = EC5Util.convertCurve(ecParams.getCurve(), ecParams.getSeed());
910+
this.paramSpec = new ECParameterSpec(curve,
911+
EC5Util.convertPoint(ecParams.getG()),
912+
ecParams.getN(), ecParams.getH().intValue());
913+
this.asn1Flag = 0; // explicit
914+
return this;
915+
}
916+
} catch (IOException e) {
917+
// fall through to treat as curve name string
918+
}
919+
}
920+
921+
this.impl_curve_name = strArg;
922+
}
923+
return this;
924+
}
925+
926+
@JRubyMethod(name = "initialize_copy", visibility = Visibility.PRIVATE)
927+
public IRubyObject initialize_copy(final IRubyObject original) {
928+
if (original instanceof Group) {
929+
final Group src = (Group) original;
930+
this.curveName = src.curveName;
931+
this.impl_curve_name = src.impl_curve_name;
932+
this.paramSpec = src.paramSpec;
933+
this.asn1Flag = src.asn1Flag;
934+
this.conversionForm = src.conversionForm;
889935
}
890936
return this;
891937
}
@@ -984,6 +1030,48 @@ public RubyString to_pem(final ThreadContext context, final IRubyObject[] args)
9841030
}
9851031
}
9861032

1033+
@JRubyMethod(name = "asn1_flag")
1034+
public IRubyObject asn1_flag(final ThreadContext context) {
1035+
return context.runtime.newFixnum(asn1Flag);
1036+
}
1037+
1038+
@JRubyMethod(name = "asn1_flag=")
1039+
public IRubyObject set_asn1_flag(final ThreadContext context, final IRubyObject flag_v) {
1040+
this.asn1Flag = (int) RubyFixnum.num2long(flag_v);
1041+
return flag_v;
1042+
}
1043+
1044+
/**
1045+
* Serializes the group as DER. For named curves (NAMED_CURVE flag set) this is the
1046+
* DER encoding of the curve OID. For explicit parameters it is the DER encoding of
1047+
* the X9.62 ECParameters SEQUENCE – matching OpenSSL's i2d_ECPKParameters().
1048+
*/
1049+
@JRubyMethod(name = "to_der")
1050+
public RubyString to_der(final ThreadContext context) {
1051+
final Ruby runtime = context.runtime;
1052+
try {
1053+
final byte[] encoded;
1054+
if ((asn1Flag & 1) != 0) { // NAMED_CURVE: encode as DER OID
1055+
final ASN1ObjectIdentifier oid = getCurveOID(getCurveName())
1056+
.orElseThrow(() -> newError(runtime, "invalid curve name: " + getCurveName()));
1057+
encoded = oid.getEncoded(ASN1Encoding.DER);
1058+
} else { // explicit parameters: encode as X9.62 ECParameters SEQUENCE
1059+
final ECParameterSpec ps = getParamSpec();
1060+
final ECCurve bcCurve = EC5Util.convertCurve(ps.getCurve());
1061+
final X9ECParameters ecParameters = new X9ECParameters(
1062+
bcCurve,
1063+
new X9ECPoint(EC5Util.convertPoint(bcCurve, ps.getGenerator()), false),
1064+
ps.getOrder(),
1065+
BigInteger.valueOf(ps.getCofactor()),
1066+
ps.getCurve().getSeed());
1067+
encoded = ecParameters.getEncoded(ASN1Encoding.DER);
1068+
}
1069+
return StringHelper.newString(runtime, encoded);
1070+
} catch (IOException e) {
1071+
throw newError(runtime, e.getMessage());
1072+
}
1073+
}
1074+
9871075
private ECParameterSpec getParamSpec() {
9881076
if (paramSpec == null) {
9891077
paramSpec = PKeyEC.getParamSpec(getCurveName());

src/test/ruby/ec/test_ec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,39 @@ def test_sign_verify
432432
assert_equal false, p256.verify("SHA256", signature1, data)
433433
end
434434

435+
def test_ec_group
436+
group1 = OpenSSL::PKey::EC::Group.new("prime256v1")
437+
key1 = OpenSSL::PKey::EC.new(group1)
438+
assert_equal group1, key1.group
439+
440+
group2 = OpenSSL::PKey::EC::Group.new(group1)
441+
assert_equal group1.to_der, group2.to_der
442+
assert_equal group1, group2
443+
group2.asn1_flag ^= OpenSSL::PKey::EC::NAMED_CURVE
444+
# explicit parameters produce different (longer) DER than a named-curve OID
445+
assert_not_equal group1.to_der, group2.to_der
446+
assert_equal group1, group2
447+
448+
group3 = group1.dup
449+
assert_equal group1.to_der, group3.to_der
450+
451+
assert group1.asn1_flag & OpenSSL::PKey::EC::NAMED_CURVE # our default
452+
453+
der = group1.to_der
454+
group4 = OpenSSL::PKey::EC::Group.new(der)
455+
group1.point_conversion_form = group4.point_conversion_form = :uncompressed
456+
assert_equal :uncompressed, group1.point_conversion_form
457+
assert_equal :uncompressed, group4.point_conversion_form
458+
assert_equal group1, group4
459+
assert_equal group1.curve_name, group4.curve_name
460+
assert_equal group1.generator.to_octet_string(:uncompressed),
461+
group4.generator.to_octet_string(:uncompressed)
462+
assert_equal group1.order, group4.order
463+
assert_equal group1.cofactor, group4.cofactor
464+
assert_equal group1.seed, group4.seed
465+
assert_equal group1.degree, group4.degree
466+
end
467+
435468
def test_group_encoding
436469
for group in @groups
437470
for meth in [:to_der, :to_pem]

0 commit comments

Comments
 (0)