Skip to content

Commit 4234dd5

Browse files
committed
[feat] partial support for PKey::EC::Point#to_octet_string(form)
1 parent 04de740 commit 4234dd5

File tree

2 files changed

+148
-27
lines changed

2 files changed

+148
-27
lines changed

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

Lines changed: 119 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.Collections;
3535
import java.util.Enumeration;
3636
import java.util.List;
37+
import java.util.Locale;
3738
import java.util.Optional;
3839
import javax.crypto.KeyAgreement;
3940
import org.bouncycastle.asn1.ASN1EncodableVector;
@@ -63,6 +64,7 @@
6364
import org.jruby.RubyModule;
6465
import org.jruby.RubyObject;
6566
import org.jruby.RubyString;
67+
import org.jruby.RubySymbol;
6668
import org.jruby.anno.JRubyClass;
6769
import org.jruby.anno.JRubyMethod;
6870
import org.jruby.exceptions.RaiseException;
@@ -695,6 +697,14 @@ public RubyString to_pem(ThreadContext context, final IRubyObject[] args) {
695697
}
696698
}
697699

700+
private enum PointConversion {
701+
COMPRESSED, UNCOMPRESSED, HYBRID;
702+
703+
String toRubyString() {
704+
return super.toString().toLowerCase(Locale.ROOT);
705+
}
706+
}
707+
698708
@JRubyClass(name = "OpenSSL::PKey::EC::Group")
699709
public static final class Group extends RubyObject {
700710

@@ -713,6 +723,9 @@ static void createGroup(final Ruby runtime, final RubyClass EC, final RubyClass
713723

714724
private transient PKeyEC key;
715725
private ECParameterSpec paramSpec;
726+
727+
private PointConversion conversionForm = PointConversion.UNCOMPRESSED;
728+
716729
private RubyString curve_name;
717730

718731
public Group(Ruby runtime, RubyClass type) {
@@ -725,11 +738,6 @@ public Group(Ruby runtime, RubyClass type) {
725738
this.paramSpec = key.publicKey.getParams();
726739
}
727740

728-
private String getCurveName() {
729-
if (key != null) return key.getCurveName();
730-
return curve_name.toString();
731-
}
732-
733741
@JRubyMethod(rest = true, visibility = Visibility.PRIVATE)
734742
public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) {
735743
final Ruby runtime = context.runtime;
@@ -743,13 +751,20 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
743751
return this;
744752
}
745753

746-
this.curve_name = ((RubyString) arg);
754+
this.curve_name = arg.convertToString();
747755

748-
// TODO PEM/DER parsing not implemented
756+
final ECNamedCurveParameterSpec ecCurveParamSpec = ECNamedCurveTable.getParameterSpec(curve_name.toString());
757+
final EllipticCurve curve = EC5Util.convertCurve(ecCurveParamSpec.getCurve(), ecCurveParamSpec.getSeed());
758+
this.paramSpec = EC5Util.convertSpec(curve, ecCurveParamSpec);
749759
}
750760
return this;
751761
}
752762

763+
private String getCurveName() {
764+
if (key != null) return key.getCurveName();
765+
return curve_name.toString();
766+
}
767+
753768
@Override
754769
@JRubyMethod(name = { "==", "eql?" })
755770
public IRubyObject op_equal(final ThreadContext context, final IRubyObject obj) {
@@ -831,13 +846,35 @@ public RubyString to_pem(final ThreadContext context, final IRubyObject[] args)
831846
}
832847
}
833848

834-
final EllipticCurve getCurve() {
849+
EllipticCurve getCurve() {
835850
if (paramSpec == null) {
836851
paramSpec = getParamSpec(getCurveName());
837852
}
838853
return paramSpec.getCurve();
839854
}
840855

856+
@JRubyMethod
857+
public RubySymbol point_conversion_form(final ThreadContext context) {
858+
return context.runtime.newSymbol(this.conversionForm.toRubyString());
859+
}
860+
861+
@JRubyMethod(name = "point_conversion_form=")
862+
public IRubyObject set_point_conversion_form(final ThreadContext context, final IRubyObject form) {
863+
this.conversionForm = parse_point_conversion_form(context.runtime, form);
864+
return form;
865+
}
866+
867+
static PointConversion parse_point_conversion_form(final Ruby runtime, final IRubyObject form) {
868+
if (form instanceof RubySymbol) {
869+
final String pointConversionForm = ((RubySymbol) form).asJavaString();
870+
if ("uncompressed".equals(pointConversionForm)) return PointConversion.UNCOMPRESSED;
871+
if ("compressed".equals(pointConversionForm)) return PointConversion.COMPRESSED;
872+
if ("hybrid".equals(pointConversionForm)) return PointConversion.HYBRID;
873+
}
874+
throw runtime.newArgumentError("unsupported point conversion form: " + form.inspect());
875+
}
876+
877+
841878
// @Override
842879
// @JRubyMethod
843880
// @SuppressWarnings("unchecked")
@@ -874,14 +911,12 @@ public Point(Ruby runtime, RubyClass type) {
874911
super(runtime, type);
875912
}
876913

877-
// private transient ECPublicKey publicKey;
878914
private ECPoint point;
879915
//private int bitLength;
880916
private Group group;
881917

882918
Point(Ruby runtime, ECPublicKey publicKey, Group group) {
883919
this(runtime, _EC(runtime).getClass("Point"));
884-
//this.publicKey = publicKey;
885920
this.point = publicKey.getW();
886921
this.group = group;
887922
}
@@ -914,7 +949,12 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
914949
this.group = (Group) arg;
915950
}
916951
if ( argc == 2 ) { // (group, bn)
917-
final byte[] encoded = ((BN) args[1]).getValue().abs().toByteArray();
952+
final byte[] encoded;
953+
if (args[1] instanceof BN) {
954+
encoded = ((BN) args[1]).getValue().abs().toByteArray();
955+
} else {
956+
encoded = args[1].convertToString().getBytes();
957+
}
918958
try {
919959
this.point = ECPointUtil.decodePoint(group.getCurve(), encoded);
920960
}
@@ -951,15 +991,54 @@ private ECPoint asECPoint() {
951991
}
952992

953993
private int bitLength() {
994+
assert group != null;
995+
assert group.paramSpec != null;
954996
return group.paramSpec.getOrder().bitLength();
955997
}
956998

999+
private PointConversion getPointConversionForm() {
1000+
if (group == null) return null;
1001+
return group.conversionForm;
1002+
}
1003+
9571004
@JRubyMethod
9581005
public BN to_bn(final ThreadContext context) {
959-
final byte[] encoded = encode(bitLength(), point);
1006+
return toBN(context, getPointConversionForm()); // group.point_conversion_form
1007+
}
1008+
1009+
@JRubyMethod
1010+
public BN to_bn(final ThreadContext context, final IRubyObject conversion_form) {
1011+
return toBN(context, Group.parse_point_conversion_form(context.runtime, conversion_form));
1012+
}
1013+
1014+
private BN toBN(final ThreadContext context, final PointConversion conversionForm) {
1015+
final byte[] encoded = encodePoint(conversionForm);
9601016
return BN.newBN(context.runtime, new BigInteger(1, encoded));
9611017
}
9621018

1019+
private byte[] encodePoint(final PointConversion conversionForm) {
1020+
final byte[] encoded;
1021+
switch (conversionForm) {
1022+
case UNCOMPRESSED:
1023+
encoded = encodeUncompressed(bitLength(), point);
1024+
break;
1025+
case COMPRESSED:
1026+
encoded = encodeCompressed(point);
1027+
break;
1028+
case HYBRID:
1029+
throw getRuntime().newNotImplementedError(":hybrid compression not implemented");
1030+
default:
1031+
throw new AssertionError("unexpected conversion form: " + conversionForm);
1032+
}
1033+
return encoded;
1034+
}
1035+
1036+
@JRubyMethod
1037+
public IRubyObject to_octet_string(final ThreadContext context, final IRubyObject conversion_form) {
1038+
final PointConversion conversionForm = Group.parse_point_conversion_form(context.runtime, conversion_form);
1039+
return StringHelper.newString(context.runtime, encodePoint(conversionForm));
1040+
}
1041+
9631042
private boolean isInfinity() {
9641043
return point == ECPoint.POINT_INFINITY;
9651044
}
@@ -986,34 +1065,49 @@ public IRubyObject inspect() {
9861065
}
9871066

9881067
static byte[] encode(final ECPublicKey pubKey) {
989-
return encode(pubKey.getParams().getOrder().bitLength(), pubKey.getW());
1068+
return encodeUncompressed(pubKey.getParams().getOrder().bitLength(), pubKey.getW());
9901069
}
9911070

992-
private static byte[] encode(final int bitLength, final ECPoint point) {
993-
if ( point == ECPoint.POINT_INFINITY ) return new byte[1];
1071+
private static byte[] encodeUncompressed(final int fieldSize, final ECPoint point) {
1072+
if (point == ECPoint.POINT_INFINITY) return new byte[1];
9941073

995-
final int bytesLength = (bitLength + 7) / 8;
996-
byte[] encoded = new byte[1 + bytesLength + bytesLength];
1074+
final int expLength = (fieldSize + 7) / 8;
1075+
1076+
byte[] encoded = new byte[1 + expLength + expLength];
9971077

9981078
encoded[0] = 0x04;
9991079

1080+
addIntBytes(point.getAffineX(), expLength, encoded, 1);
1081+
addIntBytes(point.getAffineY(), expLength, encoded, 1 + expLength);
1082+
1083+
return encoded;
1084+
}
1085+
1086+
private static byte[] encodeCompressed(final ECPoint point) {
1087+
if (point == ECPoint.POINT_INFINITY) return new byte[1];
1088+
1089+
final int bytesLength = point.getAffineX().bitLength() / 8 + 1;
1090+
1091+
byte[] encoded = new byte[1 + bytesLength];
1092+
1093+
encoded[0] = (byte) (point.getAffineY().testBit(0) ? 0x03 : 0x02);
1094+
10001095
addIntBytes(point.getAffineX(), bytesLength, encoded, 1);
1001-
addIntBytes(point.getAffineY(), bytesLength, encoded, 1 + bytesLength);
10021096

10031097
return encoded;
10041098
}
10051099

1006-
private static void addIntBytes(BigInteger i, final int length, final byte[] dest, final int destOffset) {
1007-
final byte[] bytes = i.toByteArray();
1100+
private static void addIntBytes(final BigInteger value, final int length, final byte[] dest, final int destOffset) {
1101+
final byte[] in = value.toByteArray();
10081102

1009-
if (length < bytes.length) {
1010-
System.arraycopy(bytes, bytes.length - length, dest, destOffset, length);
1103+
if (length < in.length) {
1104+
System.arraycopy(in, in.length - length, dest, destOffset, length);
10111105
}
1012-
else if (length > bytes.length) {
1013-
System.arraycopy(bytes, 0, dest, destOffset + (length - bytes.length), bytes.length);
1106+
else if (length > in.length) {
1107+
System.arraycopy(in, 0, dest, destOffset + (length - in.length), in.length);
10141108
}
10151109
else {
1016-
System.arraycopy(bytes, 0, dest, destOffset, length);
1110+
System.arraycopy(in, 0, dest, destOffset, length);
10171111
}
10181112
}
10191113

src/test/ruby/ec/test_ec.rb

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,35 @@ def test_point
7373
client_public_key_bn = OpenSSL::BN.new('58089019511196532477248433747314139754458690644712400444716868601190212265537817278966641566813745621284958192417192818318052462970895792919572995957754854')
7474

7575
binary = "\x04U\x1D6|\xA9\x14\eC\x13\x99b\x96\x9B\x94f\x8F\xB0o\xE2\xD3\xBC%\x8E\xE0Xn\xF2|R\x99b\xBD\xBFB\x8FS\xCF\x13\x7F\x8C\x03N\x96\x9D&\xB2\xE1\xBDQ\b\xCE\x94!s\x06.\xC5?\x96\xC7q\xDA\x8B\xE6"
76-
client_public_key = OpenSSL::PKey::EC::Point.new(group, client_public_key_bn)
77-
assert_equal binary, client_public_key.to_bn.to_s(2)
76+
point = OpenSSL::PKey::EC::Point.new(group, client_public_key_bn)
77+
assert_equal binary, point.to_bn.to_s(2)
78+
assert_equal binary, point.to_octet_string(:uncompressed)
79+
80+
point2 = OpenSSL::PKey::EC::Point.new(group, point.to_octet_string(:uncompressed))
81+
assert_equal binary, point2.to_bn.to_s(2)
82+
83+
compressed = "\x02U\x1D6|\xA9\x14\eC\x13\x99b\x96\x9B\x94f\x8F\xB0o\xE2\xD3\xBC%\x8E\xE0Xn\xF2|R\x99b\xBD"
84+
assert_equal compressed, point.to_octet_string(:compressed)
85+
86+
# TODO: not yet implemented
87+
# hybrid = "\x06U\x1D6|\xA9\x14\eC\x13\x99b\x96\x9B\x94f\x8F\xB0o\xE2\xD3\xBC%\x8E\xE0Xn\xF2|R\x99b\xBD\xBFB\x8FS\xCF\x13\x7F\x8C\x03N\x96\x9D&\xB2\xE1\xBDQ\b\xCE\x94!s\x06.\xC5?\x96\xC7q\xDA\x8B\xE6"
88+
# assert_equal hybrid, point.to_octet_string(:hybrid)
89+
end
90+
91+
def test_random_point
92+
group = OpenSSL::PKey::EC::Group.new("prime256v1")
93+
key = OpenSSL::PKey::EC.generate(group)
94+
point = key.public_key
95+
96+
point2 = OpenSSL::PKey::EC::Point.new(group, point.to_bn)
97+
assert_equal point, point2
98+
assert_equal point.to_bn, point2.to_bn
99+
assert_equal point.to_octet_string(:uncompressed), point2.to_octet_string(:uncompressed)
100+
101+
point3 = OpenSSL::PKey::EC::Point.new(group, point.to_octet_string(:uncompressed))
102+
assert_equal point, point3
103+
assert_equal point.to_bn, point3.to_bn
104+
assert_equal point.to_octet_string(:uncompressed), point3.to_octet_string(:uncompressed)
78105
end
79106

80107
require File.expand_path('base64.rb', File.dirname(__FILE__))

0 commit comments

Comments
 (0)