From 310dd6311d0aa0d9339b32c5d9aae891bd93da81 Mon Sep 17 00:00:00 2001 From: Hiroshi Nakamura Date: Thu, 8 Sep 2011 21:43:12 +0900 Subject: [PATCH] JRUBY-6044: Improve Ecrypted DSA pem support --- src/java/org/jruby/ext/openssl/PKeyDSA.java | 10 +- src/java/org/jruby/ext/openssl/PKeyRSA.java | 2 +- .../ext/openssl/x509store/PEMInputOutput.java | 188 +++++++----------- test/test_pkey_dsa.rb | 180 +++++++++++++++++ test/{test_pkey.rb => test_pkey_rsa.rb} | 129 ++++-------- 5 files changed, 293 insertions(+), 216 deletions(-) create mode 100644 test/test_pkey_dsa.rb rename test/{test_pkey.rb => test_pkey_rsa.rb} (79%) diff --git a/src/java/org/jruby/ext/openssl/PKeyDSA.java b/src/java/org/jruby/ext/openssl/PKeyDSA.java index 4aa3fd3..69522f6 100644 --- a/src/java/org/jruby/ext/openssl/PKeyDSA.java +++ b/src/java/org/jruby/ext/openssl/PKeyDSA.java @@ -55,6 +55,7 @@ import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; +import org.jruby.ext.openssl.impl.CipherSpec; import org.jruby.ext.openssl.x509store.PEMInputOutput; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.builtin.IRubyObject; @@ -305,21 +306,22 @@ public IRubyObject public_key() { return val; } - @JRubyMethod(name = {"export", "to_pem", "to_s"}, rest = true) + @JRubyMethod(name = { "export", "to_pem", "to_s" }, rest = true) public IRubyObject export(IRubyObject[] args) { StringWriter w = new StringWriter(); org.jruby.runtime.Arity.checkArgumentCount(getRuntime(), args, 0, 2); + CipherSpec ciph = null; char[] passwd = null; - String algo = null; if (args.length > 0 && !args[0].isNil()) { - algo = ((Cipher) args[0]).getAlgorithm(); + org.jruby.ext.openssl.Cipher c = (org.jruby.ext.openssl.Cipher) args[0]; + ciph = new CipherSpec(c.getCipher(), c.getName(), c.getKeyLen() * 8); if (args.length > 1 && !args[1].isNil()) { passwd = args[1].toString().toCharArray(); } } try { if (privKey != null) { - PEMInputOutput.writeDSAPrivateKey(w, privKey, algo, passwd); + PEMInputOutput.writeDSAPrivateKey(w, privKey, ciph, passwd); } else { PEMInputOutput.writeDSAPublicKey(w, pubKey); } diff --git a/src/java/org/jruby/ext/openssl/PKeyRSA.java b/src/java/org/jruby/ext/openssl/PKeyRSA.java index 5e76c0d..49e145b 100644 --- a/src/java/org/jruby/ext/openssl/PKeyRSA.java +++ b/src/java/org/jruby/ext/openssl/PKeyRSA.java @@ -376,7 +376,7 @@ public IRubyObject to_text() { return getRuntime().newString(result.toString()); } - @JRubyMethod(name = {"export", "to_pem", "to_s"}, rest = true) + @JRubyMethod(name = { "export", "to_pem", "to_s" }, rest = true) public IRubyObject export(IRubyObject[] args) { StringWriter w = new StringWriter(); org.jruby.runtime.Arity.checkArgumentCount(getRuntime(), args, 0, 2); diff --git a/src/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java b/src/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java index 32fa07e..b22f31d 100644 --- a/src/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java +++ b/src/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java @@ -757,142 +757,97 @@ public static void writeX509Request(Writer _out, PKCS10CertificationRequestExt o } } - public static void writeDSAPrivateKey(Writer _out, DSAPrivateKey obj, String algo, char[] f) throws IOException { + public static void writeDSAPrivateKey(Writer _out, DSAPrivateKey obj, CipherSpec cipher, char[] passwd) throws IOException { BufferedWriter out = makeBuffered(_out); - ByteArrayInputStream bIn = new ByteArrayInputStream(getEncoded(obj)); - ASN1InputStream aIn = new ASN1InputStream(bIn); - PrivateKeyInfo info = new PrivateKeyInfo((ASN1Sequence)aIn.readObject()); - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - ASN1OutputStream aOut = new ASN1OutputStream(bOut); + ByteArrayInputStream bIn = new ByteArrayInputStream(getEncoded(obj)); + ASN1InputStream aIn = new ASN1InputStream(bIn); + PrivateKeyInfo info = new PrivateKeyInfo((ASN1Sequence) aIn.readObject()); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ASN1OutputStream aOut = new ASN1OutputStream(bOut); - DSAParameter p = DSAParameter.getInstance(info.getAlgorithmId().getParameters()); + DSAParameter p = DSAParameter.getInstance(info.getAlgorithmId().getParameters()); ASN1EncodableVector v = new ASN1EncodableVector(); - v.add(new DERInteger(0)); v.add(new DERInteger(p.getP())); v.add(new DERInteger(p.getQ())); v.add(new DERInteger(p.getG())); - + BigInteger x = obj.getX(); BigInteger y = p.getG().modPow(x, p.getP()); - + v.add(new DERInteger(y)); v.add(new DERInteger(x)); - + aOut.writeObject(new DERSequence(v)); byte[] encoding = bOut.toByteArray(); - if(algo != null && f != null) { - byte[] salt = new byte[8]; - byte[] encData = null; - random.nextBytes(salt); - OpenSSLPBEParametersGenerator pGen = new OpenSSLPBEParametersGenerator(); - pGen.init(PBEParametersGenerator.PKCS5PasswordToBytes(f), salt); - SecretKey secretKey = null; - if (algo.equalsIgnoreCase("DESede/CBC/PKCS5Padding")) { - // generate key - int keyLength = 24; - KeyParameter param = (KeyParameter) pGen.generateDerivedParameters(keyLength * 8); - secretKey = new SecretKeySpec(param.getKey(), "DESede"); - } else { - throw new IOException("unknown algorithm in write_DSAPrivateKey: " + algo); - } - - // cipher - try { - Cipher c = Cipher.getInstance("DESede/CBC/PKCS5Padding"); - c.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(salt)); - encData = c.doFinal(encoding); - } catch (Exception e) { - throw new IOException("exception using cipher: " + e.toString()); - } - - // write the data - out.write(BEF_G + PEM_STRING_DSA + AFT); - out.newLine(); - out.write("Proc-Type: 4,ENCRYPTED"); - out.newLine(); - out.write("DEK-Info: DES-EDE3-CBC,"); - writeHexEncoded(out,salt); - out.newLine(); - out.newLine(); - writeEncoded(out,encData); - out.write(BEF_E + PEM_STRING_DSA + AFT); - out.flush(); + if (cipher != null && passwd != null) { + writePemEncrypted(out, PEM_STRING_DSA, encoding, cipher, passwd); } else { - out.write(BEF_G + PEM_STRING_DSA + AFT); - out.newLine(); - writeEncoded(out,encoding); - out.write(BEF_E + PEM_STRING_DSA + AFT); - out.newLine(); - out.flush(); + writePemPlain(out, PEM_STRING_DSA, encoding); } } public static void writeRSAPrivateKey(Writer _out, RSAPrivateCrtKey obj, CipherSpec cipher, char[] passwd) throws IOException { - assert(obj != null); + assert (obj != null); BufferedWriter out = makeBuffered(_out); - RSAPrivateKeyStructure keyStruct = new RSAPrivateKeyStructure( - obj.getModulus(), - obj.getPublicExponent(), - obj.getPrivateExponent(), - obj.getPrimeP(), - obj.getPrimeQ(), - obj.getPrimeExponentP(), - obj.getPrimeExponentQ(), - obj.getCrtCoefficient()); - - // convert to bytearray + RSAPrivateKeyStructure keyStruct = new RSAPrivateKeyStructure(obj.getModulus(), obj.getPublicExponent(), obj.getPrivateExponent(), obj.getPrimeP(), + obj.getPrimeQ(), obj.getPrimeExponentP(), obj.getPrimeExponentQ(), obj.getCrtCoefficient()); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - ASN1OutputStream aOut = new ASN1OutputStream(bOut); - + ASN1OutputStream aOut = new ASN1OutputStream(bOut); aOut.writeObject(keyStruct); aOut.close(); - byte[] encoding = bOut.toByteArray(); - if(cipher != null && passwd != null) { - Cipher c = cipher.getCipher(); - String algoBase = c.getAlgorithm(); - if (algoBase.indexOf('/') != -1) { - algoBase = algoBase.split("/")[0]; - } - byte[] iv = new byte[c.getBlockSize()]; - random.nextBytes(iv); - byte[] salt = new byte[8]; - System.arraycopy(iv, 0, salt, 0, 8); - OpenSSLPBEParametersGenerator pGen = new OpenSSLPBEParametersGenerator(); - pGen.init(PBEParametersGenerator.PKCS5PasswordToBytes(passwd), salt); - KeyParameter param = (KeyParameter) pGen.generateDerivedParameters(cipher.getKeyLenInBits()); - SecretKey secretKey = new SecretKeySpec(param.getKey(), algoBase); - byte[] encData = null; - try { - c.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); - encData = c.doFinal(encoding); - } catch (GeneralSecurityException gse) { - throw new IOException("exception using cipher: " + gse.toString()); - } - - // write the data - out.write(BEF_G + PEM_STRING_RSA + AFT); - out.newLine(); - out.write("Proc-Type: 4,ENCRYPTED"); - out.newLine(); - out.write("DEK-Info: " + cipher.getOsslName() + ","); - writeHexEncoded(out,iv); - out.newLine(); - out.newLine(); - writeEncoded(out,encData); - out.write(BEF_E + PEM_STRING_RSA + AFT); - out.flush(); + if (cipher != null && passwd != null) { + writePemEncrypted(out, PEM_STRING_RSA, encoding, cipher, passwd); } else { - out.write(BEF_G + PEM_STRING_RSA + AFT); - out.newLine(); - writeEncoded(out,encoding); - out.write(BEF_E + PEM_STRING_RSA + AFT); - out.newLine(); - out.flush(); + writePemPlain(out, PEM_STRING_RSA, encoding); + } + } + + private static void writePemPlain(BufferedWriter out, String pemHeader, byte[] encoding) throws IOException { + out.write(BEF_G + pemHeader + AFT); + out.newLine(); + writeEncoded(out, encoding); + out.write(BEF_E + pemHeader + AFT); + out.newLine(); + out.flush(); + } + + private static void writePemEncrypted(BufferedWriter out, String pemHeader, byte[] encoding, CipherSpec cipher, char[] passwd) throws IOException { + Cipher c = cipher.getCipher(); + String algoBase = c.getAlgorithm(); + if (algoBase.indexOf('/') != -1) { + algoBase = algoBase.split("/")[0]; } + byte[] iv = new byte[c.getBlockSize()]; + random.nextBytes(iv); + byte[] salt = new byte[8]; + System.arraycopy(iv, 0, salt, 0, 8); + OpenSSLPBEParametersGenerator pGen = new OpenSSLPBEParametersGenerator(); + pGen.init(PBEParametersGenerator.PKCS5PasswordToBytes(passwd), salt); + KeyParameter param = (KeyParameter) pGen.generateDerivedParameters(cipher.getKeyLenInBits()); + SecretKey secretKey = new SecretKeySpec(param.getKey(), algoBase); + byte[] encData = null; + try { + c.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); + encData = c.doFinal(encoding); + } catch (GeneralSecurityException gse) { + throw new IOException("exception using cipher: " + gse.toString()); + } + out.write(BEF_G + pemHeader + AFT); + out.newLine(); + out.write("Proc-Type: 4,ENCRYPTED"); + out.newLine(); + out.write("DEK-Info: " + cipher.getOsslName() + ","); + writeHexEncoded(out, iv); + out.newLine(); + out.newLine(); + writeEncoded(out, encData); + out.write(BEF_E + pemHeader + AFT); + out.flush(); } public static void writeDHParameters(Writer _out, DHParameterSpec params) throws IOException { @@ -1003,12 +958,11 @@ private static PublicKey readPublicKey(BufferedReader in, String endMarker) thro /** * Read a Key Pair */ - private static KeyPair readKeyPair(BufferedReader _in, char[] passwd, String type,String endMarker) - throws Exception { - boolean isEncrypted = false; - String line = null; - String dekInfo = null; - StringBuffer buf = new StringBuffer(); + private static KeyPair readKeyPair(BufferedReader _in, char[] passwd, String type, String endMarker) throws Exception { + boolean isEncrypted = false; + String line = null; + String dekInfo = null; + StringBuffer buf = new StringBuffer(); while ((line = _in.readLine()) != null) { if (line.startsWith("Proc-Type: 4,ENCRYPTED")) { @@ -1042,8 +996,8 @@ private static KeyPair readKeyPair(BufferedReader _in, char[] passwd, String typ throw new IOException("Illegal IV length"); } byte[] salt = new byte[8]; - System.arraycopy(iv, 0, salt, 0, 8); - OpenSSLPBEParametersGenerator pGen = new OpenSSLPBEParametersGenerator(); + System.arraycopy(iv, 0, salt, 0, 8); + OpenSSLPBEParametersGenerator pGen = new OpenSSLPBEParametersGenerator(); pGen.init(PBEParametersGenerator.PKCS5PasswordToBytes(passwd), salt); KeyParameter param = (KeyParameter) pGen.generateDerivedParameters(keyLen * 8); SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(param.getKey(), realName); diff --git a/test/test_pkey_dsa.rb b/test/test_pkey_dsa.rb new file mode 100644 index 0000000..b4458fe --- /dev/null +++ b/test/test_pkey_dsa.rb @@ -0,0 +1,180 @@ +require "openssl" +require "test/unit" + +class TestPKeyDSA < Test::Unit::TestCase + def test_can_generate_dsa_key + OpenSSL::PKey::DSA.generate(512) + end + + # jruby-openssl/0.6 causes NPE + def test_generate_pkey_dsa_empty + assert_nothing_raised do + OpenSSL::PKey::DSA.new.to_pem + end + end + + # jruby-openssl/0.6 ignores fixnum arg => to_pem returned 65 bytes with 'MAA=' + def test_generate_pkey_dsa_length + assert(OpenSSL::PKey::DSA.new(512).to_pem.size > 100) + end + + # jruby-openssl/0.6 returns nil for DSA#to_text + def test_generate_pkey_dsa_to_text + assert_match( + /Private-Key: \(512 bit\)/, + OpenSSL::PKey::DSA.new(512).to_text + ) + end + + def test_load_pkey_dsa + pkey = OpenSSL::PKey::DSA.new(512) + assert_equal(pkey.to_pem, OpenSSL::PKey::DSA.new(pkey.to_pem).to_pem) + end + + def test_load_pkey_dsa_public + pkey = OpenSSL::PKey::DSA.new(512).public_key + assert_equal(pkey.to_pem, OpenSSL::PKey::DSA.new(pkey.to_pem).to_pem) + end + + def test_load_pkey_dsa_der + pkey = OpenSSL::PKey::DSA.new(512) + assert_equal(pkey.to_der, OpenSSL::PKey::DSA.new(pkey.to_der).to_der) + end + + def test_load_pkey_dsa_public_der + pkey = OpenSSL::PKey::DSA.new(512).public_key + assert_equal(pkey.to_der, OpenSSL::PKey::DSA.new(pkey.to_der).to_der) + end + + def test_load_pkey_dsa_net_ssh + blob = "0\201\367\002\001\000\002A\000\203\316/\037u\272&J\265\003l3\315d\324h\372{\t8\252#\331_\026\006\035\270\266\255\343\353Z\302\276\335\336\306\220\375\202L\244\244J\206>\346\b\315\211\302L\246x\247u\a\376\366\345\302\016#\002\025\000\244\274\302\221Og\275/\302+\356\346\360\024\373wI\2573\361\002@\027\215\270r*\f\213\350C\245\021:\350 \006\\\376\345\022`\210b\262\3643\023XLKS\320\370\002\276\347A\nU\204\276\324\256`=\026\240\330\306J\316V\213\024\e\030\215\355\006\037q\337\356ln\002@\017\257\034\f\260\333'S\271#\237\230E\321\312\027\021\226\331\251Vj\220\305\316\036\v\266+\000\230\270\177B\003?t\a\305]e\344\261\334\023\253\323\251\223M\2175)a(\004\"lI8\312\303\307\a\002\024_\aznW\345\343\203V\326\246ua\203\376\201o\350\302\002" + pkey = OpenSSL::PKey::DSA.new(blob) + assert_equal(blob, pkey.to_der) + end + + def test_load_dsa_des_encrypted + password = 'pass' + pkey = OpenSSL::PKey::DSA.generate(512) + cipher = OpenSSL::Cipher::Cipher.new('des-cbc') + pem = pkey.to_pem(cipher, password) + assert_equal(pkey.g, OpenSSL::PKey::DSA.new(pem, password).g) + end + + def test_load_dsa_3des_encrypted + password = 'pass' + pkey = OpenSSL::PKey::DSA.generate(512) + cipher = OpenSSL::Cipher::Cipher.new('des-ede3-cbc') + pem = pkey.to_pem(cipher, password) + assert_equal(pkey.g, OpenSSL::PKey::DSA.new(pem, password).g) + end + + def test_load_dsa_aes_encrypted + password = 'pass' + pkey = OpenSSL::PKey::DSA.generate(512) + cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc') + pem = pkey.to_pem(cipher, password) + assert_equal(pkey.g, OpenSSL::PKey::DSA.new(pem, password).g) + end + + CRUBY_DES_DSA_PEM = < to_pem returned 65 bytes with 'MAA=' - def test_generate_pkey_dsa_length - assert(OpenSSL::PKey::DSA.new(512).to_pem.size > 100) - end - - # jruby-openssl/0.6 returns nil for DSA#to_text - def test_generate_pkey_dsa_to_text - assert_match( - /Private-Key: \(512 bit\)/, - OpenSSL::PKey::DSA.new(512).to_text - ) - end - - def test_load_pkey_dsa - pkey = OpenSSL::PKey::DSA.new(512) - assert_equal(pkey.to_pem, OpenSSL::PKey::DSA.new(pkey.to_pem).to_pem) - end - - def test_load_pkey_dsa_public - pkey = OpenSSL::PKey::DSA.new(512).public_key - assert_equal(pkey.to_pem, OpenSSL::PKey::DSA.new(pkey.to_pem).to_pem) - end - - def test_load_pkey_dsa_der - pkey = OpenSSL::PKey::DSA.new(512) - assert_equal(pkey.to_der, OpenSSL::PKey::DSA.new(pkey.to_der).to_der) + def test_load_rsa_des_encrypted + password = 'pass' + pkey = OpenSSL::PKey::RSA.generate(1024) + cipher = OpenSSL::Cipher::Cipher.new('des-cbc') + pem = pkey.to_pem(cipher, password) + assert_equal(pkey.n, OpenSSL::PKey::RSA.new(pem, password).n) end - def test_load_pkey_dsa_public_der - pkey = OpenSSL::PKey::DSA.new(512).public_key - assert_equal(pkey.to_der, OpenSSL::PKey::DSA.new(pkey.to_der).to_der) + def test_load_rsa_3des_encrypted + password = 'pass' + pkey = OpenSSL::PKey::RSA.generate(1024) + cipher = OpenSSL::Cipher::Cipher.new('des-ede3-cbc') + pem = pkey.to_pem(cipher, password) + assert_equal(pkey.n, OpenSSL::PKey::RSA.new(pem, password).n) end - def test_load_pkey_dsa_net_ssh - blob = "0\201\367\002\001\000\002A\000\203\316/\037u\272&J\265\003l3\315d\324h\372{\t8\252#\331_\026\006\035\270\266\255\343\353Z\302\276\335\336\306\220\375\202L\244\244J\206>\346\b\315\211\302L\246x\247u\a\376\366\345\302\016#\002\025\000\244\274\302\221Og\275/\302+\356\346\360\024\373wI\2573\361\002@\027\215\270r*\f\213\350C\245\021:\350 \006\\\376\345\022`\210b\262\3643\023XLKS\320\370\002\276\347A\nU\204\276\324\256`=\026\240\330\306J\316V\213\024\e\030\215\355\006\037q\337\356ln\002@\017\257\034\f\260\333'S\271#\237\230E\321\312\027\021\226\331\251Vj\220\305\316\036\v\266+\000\230\270\177B\003?t\a\305]e\344\261\334\023\253\323\251\223M\2175)a(\004\"lI8\312\303\307\a\002\024_\aznW\345\343\203V\326\246ua\203\376\201o\350\302\002" - pkey = OpenSSL::PKey::DSA.new(blob) - assert_equal(blob, pkey.to_der) + def test_load_rsa_aes_encrypted + password = 'pass' + pkey = OpenSSL::PKey::RSA.generate(1024) + cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc') + pem = pkey.to_pem(cipher, password) + assert_equal(pkey.n, OpenSSL::PKey::RSA.new(pem, password).n) end - CRUBY_DES_PEM = <