Skip to content

Fix for PKCS8 EC private key support #267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/main/java/org/jruby/ext/openssl/PKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public static IRubyObject read(final ThreadContext context, IRubyObject recv, IR
(DSAPrivateKey) keyPair.getPrivate(), (DSAPublicKey) keyPair.getPublic()
);
}
if ( "ECDSA".equals(alg) ) {
if ( "EC".equals(alg) ) {
return new PKeyEC(runtime, _PKey(runtime).getClass("EC"),
(PrivateKey) keyPair.getPrivate(), (PublicKey) keyPair.getPublic()
);
Expand Down Expand Up @@ -165,7 +165,7 @@ public static IRubyObject read(final ThreadContext context, IRubyObject recv, IR
if ( "DSA".equals(pubKey.getAlgorithm()) ) {
return new PKeyDSA(runtime, (DSAPublicKey) pubKey);
}
if ( "ECDSA".equals(pubKey.getAlgorithm()) ) {
if ( "EC".equals(pubKey.getAlgorithm()) ) {
return new PKeyEC(runtime, pubKey);
}
}
Expand Down Expand Up @@ -197,6 +197,8 @@ public IRubyObject initialize(ThreadContext context) {

public String getAlgorithm() { return "NONE"; }

public String getKeyType() { return getAlgorithm(); }

public boolean isPrivateKey() { return getPrivateKey() != null; }

public abstract RubyString to_der() ;
Expand Down
17 changes: 10 additions & 7 deletions src/main/java/org/jruby/ext/openssl/PKeyEC.java
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ public PKeyEC(Ruby runtime, RubyClass type) {
@Override
public String getAlgorithm() { return "ECDSA"; }

@Override
public String getKeyType() { return "EC"; }

@JRubyMethod(rest = true, visibility = Visibility.PRIVATE)
public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args, Block block) {
final Ruby runtime = context.runtime;
Expand Down Expand Up @@ -254,13 +257,13 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
Object key = null;
final KeyFactory ecdsaFactory;
try {
ecdsaFactory = SecurityHelper.getKeyFactory("ECDSA");
ecdsaFactory = SecurityHelper.getKeyFactory("EC");
}
catch (NoSuchAlgorithmException e) {
throw runtime.newRuntimeError("unsupported key algorithm (ECDSA)");
throw runtime.newRuntimeError("unsupported key algorithm (EC)");
}
catch (RuntimeException e) {
throw runtime.newRuntimeError("unsupported key algorithm (ECDSA) " + e);
throw runtime.newRuntimeError("unsupported key algorithm (EC) " + e);
}
// TODO: ugly NoClassDefFoundError catching for no BC env. How can we remove this?
boolean noClassDef = false;
Expand Down Expand Up @@ -380,7 +383,7 @@ public PKeyEC generate_key(final ThreadContext context) {
// final ECDomainParameters params = getDomainParameters();
try {
ECGenParameterSpec genSpec = new ECGenParameterSpec(getCurveName());
KeyPairGenerator gen = SecurityHelper.getKeyPairGenerator("ECDSA"); // "BC"
KeyPairGenerator gen = SecurityHelper.getKeyPairGenerator("EC"); // "BC"
gen.initialize(genSpec, new SecureRandom());
KeyPair pair = gen.generateKeyPair();
this.publicKey = (ECPublicKey) pair.getPublic();
Expand Down Expand Up @@ -517,7 +520,7 @@ public IRubyObject set_public_key(final ThreadContext context, final IRubyObject
final Point point = (Point) arg;
ECPublicKeySpec keySpec = new ECPublicKeySpec(point.asECPoint(), getParamSpec());
try {
this.publicKey = (ECPublicKey) SecurityHelper.getKeyFactory("ECDSA").generatePublic(keySpec);
this.publicKey = (ECPublicKey) SecurityHelper.getKeyFactory("EC").generatePublic(keySpec);
return arg;
}
catch (GeneralSecurityException ex) {
Expand Down Expand Up @@ -555,7 +558,7 @@ public IRubyObject set_private_key(final ThreadContext context, final IRubyObjec
}
ECPrivateKeySpec keySpec = new ECPrivateKeySpec(s, getParamSpec());
try {
this.privateKey = SecurityHelper.getKeyFactory("ECDSA").generatePrivate(keySpec);
this.privateKey = SecurityHelper.getKeyFactory("EC").generatePrivate(keySpec);
return arg;
}
catch (GeneralSecurityException ex) {
Expand Down Expand Up @@ -942,4 +945,4 @@ else if (length > bytes.length) {
}
}

}
}
10 changes: 5 additions & 5 deletions src/main/java/org/jruby/ext/openssl/SSLContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -994,12 +994,12 @@ private class InternalContext {

if ( pKey != null && xCert != null ) {
this.privateKey = pKey.getPrivateKey();
this.keyAlgorithm = pKey.getAlgorithm();
this.keyType = pKey.getKeyType();
this.cert = xCert.getAuxCert();
}
else {
this.privateKey = null;
this.keyAlgorithm = null;
this.keyType = null;
this.cert = null;
}

Expand Down Expand Up @@ -1047,7 +1047,7 @@ void initSSLContext(final ThreadContext context) throws KeyManagementException {

final Store store;
final X509AuxCertificate cert;
final String keyAlgorithm;
final String keyType;
final PrivateKey privateKey;

final int verifyMode;
Expand Down Expand Up @@ -1098,7 +1098,7 @@ public String chooseEngineClientAlias(String[] keyType, java.security.Principal[
if (internalContext.privateKey == null) return null;

for (int i = 0; i < keyType.length; i++) {
if (keyType[i].equalsIgnoreCase(internalContext.keyAlgorithm)) {
if (keyType[i].equalsIgnoreCase(internalContext.keyType)) {
return keyType[i];
}
}
Expand All @@ -1109,7 +1109,7 @@ public String chooseEngineClientAlias(String[] keyType, java.security.Principal[
public String chooseEngineServerAlias(String keyType, java.security.Principal[] issuers, javax.net.ssl.SSLEngine engine) {
if (internalContext.privateKey == null) return null;

if (keyType.equalsIgnoreCase(internalContext.keyAlgorithm)) {
if (keyType.equalsIgnoreCase(internalContext.keyType)) {
return keyType;
}
return null;
Expand Down
8 changes: 3 additions & 5 deletions src/main/java/org/jruby/ext/openssl/impl/PKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ else if ( type.equals("DSA") ) {
privSpec = new DSAPrivateKeySpec(x.getValue(), p.getValue(), q.getValue(), g.getValue());
pubSpec = new DSAPublicKeySpec(y.getValue(), p.getValue(), q.getValue(), g.getValue());
}
else if ( type.equals("ECDSA") ) {
else if ( type.equals("EC") ) {
return readECPrivateKey(input);
}
else {
Expand Down Expand Up @@ -278,7 +278,7 @@ public static DHParameterSpec readDHParameter(final byte[] input) throws IOExcep

public static KeyPair readECPrivateKey(final byte[] input)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
return readECPrivateKey(SecurityHelper.getKeyFactory("ECDSA"), input);
return readECPrivateKey(SecurityHelper.getKeyFactory("EC"), input);
}

public static KeyPair readECPrivateKey(final KeyFactory ecFactory, final byte[] input)
Expand All @@ -290,7 +290,7 @@ public static KeyPair readECPrivateKey(final KeyFactory ecFactory, final byte[]
SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pKey.getPublicKey().getBytes());
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privInfo.getEncoded());
X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded());
//KeyFactory fact = KeyFactory.getInstance("ECDSA", provider);
//KeyFactory fact = KeyFactory.getInstance("EC", provider);

ECPrivateKey privateKey = (ECPrivateKey) ecFactory.generatePrivate(privSpec);
if ( algId.getParameters() instanceof ASN1ObjectIdentifier ) {
Expand Down Expand Up @@ -383,5 +383,3 @@ public static byte[] toDerDHKey(BigInteger p, BigInteger g) throws IOException {
return new DLSequence(vec).getEncoded();
}
}


21 changes: 12 additions & 9 deletions src/main/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ else if ( line.indexOf(BEG_STRING_DSA) != -1 ) {
}
else if ( line.indexOf(BEG_STRING_ECPRIVATEKEY) != -1) {
try {
return readKeyPair(reader, passwd, "ECDSA", BEF_E + PEM_STRING_ECPRIVATEKEY);
return readKeyPair(reader, passwd, "EC", BEF_E + PEM_STRING_ECPRIVATEKEY);
}
catch (Exception e) {
throw mapReadException("problem creating DSA private key: ", e);
Expand All @@ -349,9 +349,10 @@ else if ( line.indexOf(BEG_STRING_ECPRIVATEKEY) != -1) {
else if ( line.indexOf(BEG_STRING_PKCS8INF) != -1) {
try {
byte[] bytes = readBase64Bytes(reader, BEF_E + PEM_STRING_PKCS8INF);
PrivateKeyInfo info = PrivateKeyInfo.getInstance(bytes);
String type = getPrivateKeyTypeFromObjectId(info.getPrivateKeyAlgorithm().getAlgorithm());
return org.jruby.ext.openssl.impl.PKey.readPrivateKey(((ASN1Object) info.parsePrivateKey()).getEncoded(ASN1Encoding.DER), type);
PrivateKeyInfo pInfo = PrivateKeyInfo.getInstance(bytes);
KeyFactory keyFactory = getKeyFactory( pInfo.getPrivateKeyAlgorithm() );
PrivateKey pKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pInfo.getEncoded()));
return new KeyPair(null, pKey);
Copy link
Contributor Author

@tsaarni tsaarni Oct 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the approach proposed here does not try to derive the public key from private key, like org.jruby.ext.openssl.impl.PKey.readPrivateKey() attempted to do. Instead, it takes the same approach as reading encrypted PKCS#8 has done, here few lines below

This approach worked for me in TLS tests.

}
catch (Exception e) {
throw mapReadException("problem creating private key: ", e);
Expand Down Expand Up @@ -605,7 +606,7 @@ public static ECPublicKey readECPubKey(Reader in) throws IOException {
while ( ( line = reader.readLine() ) != null ) {
if ( line.indexOf(BEG_STRING_EC_PUBLIC) != -1 ) {
try {
return (ECPublicKey) readPublicKey(reader, "ECDSA", BEF_E + "EC PUBLIC KEY");
return (ECPublicKey) readPublicKey(reader, "EC", BEF_E + "EC PUBLIC KEY");
}
catch (Exception e) {
throw mapReadException("problem creating ECDSA public key: ", e);
Expand All @@ -621,7 +622,7 @@ public static ECPublicKey readECPublicKey(final Reader in, final char[] passwd)
while ( ( line = reader.readLine() ) != null ) {
if ( line.indexOf(BEG_STRING_PUBLIC) != -1 ) {
try {
return (ECPublicKey) readPublicKey(reader, "ECDSA", BEF_E + PEM_STRING_PUBLIC);
return (ECPublicKey) readPublicKey(reader, "EC", BEF_E + PEM_STRING_PUBLIC);
}
catch (Exception e) {
throw mapReadException("problem creating ECDSA public key: ", e);
Expand All @@ -638,7 +639,7 @@ public static KeyPair readECPrivateKey(final Reader in, final char[] passwd)
while ( ( line = reader.readLine() ) != null ) {
if ( line.indexOf(BEG_STRING_EC) != -1 ) {
try {
return readKeyPair(reader, passwd, "ECDSA", BEF_E + "EC PRIVATE KEY");
return readKeyPair(reader, passwd, "EC", BEF_E + "EC PRIVATE KEY");
}
catch (Exception e) {
throw mapReadException("problem creating ECDSA private key: ", e);
Expand Down Expand Up @@ -1171,6 +1172,8 @@ public static void writeDHParameters(Writer _out, DHParameterSpec params) throws
private static String getPrivateKeyTypeFromObjectId(ASN1ObjectIdentifier oid) {
if ( ASN1Registry.oid2nid(oid) == ASN1Registry.NID_rsaEncryption ) {
return "RSA";
} else if ( ASN1Registry.oid2nid(oid) == ASN1Registry.NID_X9_62_id_ecPublicKey ) {
return "EC";
} else {
return "DSA";
}
Expand Down Expand Up @@ -1226,7 +1229,7 @@ private static PublicKey readPublicKey(BufferedReader in, String alg, String end

private static PublicKey readPublicKey(BufferedReader in, String endMarker) throws IOException {
byte[] input = readBase64Bytes(in, endMarker);
String[] algs = { "RSA", "DSA", "ECDSA" };
String[] algs = { "RSA", "DSA", "EC" };
for (int i = 0; i < algs.length; i++) {
PublicKey key = readPublicKey(input, algs[i], endMarker);
if (key != null) {
Expand Down Expand Up @@ -1488,7 +1491,7 @@ public static KeyFactory getKeyFactory(final AlgorithmIdentifier algId)

String algorithm = null;
if ( X9ObjectIdentifiers.id_ecPublicKey.equals(algIdentifier) ) {
algorithm = "ECDSA";
algorithm = "EC";
}
else if ( PKCSObjectIdentifiers.rsaEncryption.equals(algIdentifier) ) {
algorithm = "RSA";
Expand Down
5 changes: 5 additions & 0 deletions src/test/ruby/ec/private_key_pkcs8.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgUmgU1rG7E9WJmB4A
D1RZ+PP+aYEH2ZZxWTGVR0gDr/qhRANCAAR5d0hOX+W8RznN62sAzIeozl4OBl6K
nKdpKKiZTAua05NCaWJR5mGnrCyn4g+sQV4pUgmp9NzSMwmXAzJt3GK9
-----END PRIVATE KEY-----
10 changes: 9 additions & 1 deletion src/test/ruby/ec/test_ec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ def test_read_pem2
#puts signature.inspect
end

def test_read_pkcs8_with_ec
key_file = File.join(File.dirname(__FILE__), 'private_key_pkcs8.pem')

key = OpenSSL::PKey::read(File.read(key_file))
assert_equal '37273549501637553234010607973347901861080883009977847480473501706546896416762', key.private_key.to_s
assert_empty key.public_key.to_s
end

def test_point
group = OpenSSL::PKey::EC::Group.new('prime256v1')
client_public_key_bn = OpenSSL::BN.new('58089019511196532477248433747314139754458690644712400444716868601190212265537817278966641566813745621284958192417192818318052462970895792919572995957754854')
Expand Down Expand Up @@ -311,4 +319,4 @@ def test_dsa_sign_verify
# end
# end

end
end