Skip to content

Commit

Permalink
basic support for prompting for PEM password (currently working for R…
Browse files Browse the repository at this point in the history
…SA/DSA priv.key)
kares committed Jan 11, 2016
1 parent b25de83 commit 098ad46
Showing 5 changed files with 123 additions and 42 deletions.
29 changes: 29 additions & 0 deletions src/main/java/org/jruby/ext/openssl/PKey.java
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
@@ -350,6 +351,34 @@ protected static char[] password(final IRubyObject pass) {
return null;
}

protected static char[] passwordPrompt(final ThreadContext context) {
return passwordPrompt(context, "Enter PEM pass phrase:");
}

protected static char[] passwordPrompt(final ThreadContext context, final String prompt) {
final RubyModule Kernel = context.runtime.getKernel();
// NOTE: just a fast and simple print && gets - hopefully better than nothing!
Kernel.callMethod("print", context.runtime.newString(prompt));
final RubyString gets = Kernel.callMethod(context, "gets").convertToString();
gets.chomp_bang(context);
return gets.toString().toCharArray();
}

protected static boolean ttySTDIN(final ThreadContext context) {
final IRubyObject stdin = context.runtime.getGlobalVariables().get("$stdin");
if ( stdin == null || stdin.isNil() ) return false;
try {
final IRubyObject tty = stdin.callMethod(context, "tty?");
return ! tty.isNil() && ! ( tty == context.runtime.getFalse() );
}
catch (RaiseException ex) { return false; }
}

static Object readPrivateKey(final RubyString str, final char[] passwd)
throws PEMInputOutput.PasswordRequiredException, IOException {
return PEMInputOutput.readPrivateKey(new StringReader(str.toString()), passwd);
}

protected static RubyString readInitArg(final ThreadContext context, IRubyObject arg) {
return StringHelper.readPossibleDERInput(context, arg);
}
9 changes: 6 additions & 3 deletions src/main/java/org/jruby/ext/openssl/PKeyDH.java
Original file line number Diff line number Diff line change
@@ -144,9 +144,11 @@ public synchronized IRubyObject initialize(final ThreadContext context, final IR
}
this.dh_p = spec.getP();
this.dh_g = spec.getG();
} catch (NoClassDefFoundError e) {
}
catch (NoClassDefFoundError e) {
throw newDHError(runtime, bcExceptionMessage(e));
} catch (IOException e) {
}
catch (IOException e) {
throw runtime.newIOErrorFromException(e);
}
} else {
@@ -156,7 +158,8 @@ public synchronized IRubyObject initialize(final ThreadContext context, final IR
BigInteger p;
try {
p = generateP(bits, gval);
} catch(IllegalArgumentException e) {
}
catch(IllegalArgumentException e) {
throw runtime.newArgumentError(e.getMessage());
}
BigInteger g = BigInteger.valueOf(gval);
19 changes: 16 additions & 3 deletions src/main/java/org/jruby/ext/openssl/PKeyDSA.java
Original file line number Diff line number Diff line change
@@ -181,18 +181,26 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
final KeyFactory dsaFactory;
try {
dsaFactory = SecurityHelper.getKeyFactory("DSA");
} catch (NoSuchAlgorithmException e) {
}
catch (NoSuchAlgorithmException e) {
throw runtime.newRuntimeError("unsupported key algorithm (DSA)");
} catch (RuntimeException e) {
}
catch (RuntimeException e) {
throw runtime.newRuntimeError("unsupported key algorithm (DSA) " + 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 = PEMInputOutput.readDSAPrivateKey(new StringReader(str.toString()), passwd);
key = readPrivateKey(str, 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 ) { // PEM_read_bio_DSAPublicKey
@@ -263,6 +271,11 @@ else if ( key instanceof DSAPublicKey ) {
return this;
}

//private static Object readPrivateKey(final RubyString str, final char[] passwd)
// throws PEMInputOutput.PasswordRequiredException, IOException {
// return PEMInputOutput.readDSAPrivateKey(new StringReader(str.toString()), passwd);
//}

@JRubyMethod(name = "public?")
public RubyBoolean public_p() {
return publicKey != null ? getRuntime().getTrue() : getRuntime().getFalse();
16 changes: 12 additions & 4 deletions src/main/java/org/jruby/ext/openssl/PKeyRSA.java
Original file line number Diff line number Diff line change
@@ -209,7 +209,7 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
if ( arg instanceof RubyFixnum ) {
int keysize = RubyNumeric.fix2int((RubyFixnum) arg);
BigInteger exp = RSAKeyGenParameterSpec.F4;
if (null != pass && !pass.isNil()) {
if ( pass != null && ! pass.isNil() ) {
exp = BigInteger.valueOf(RubyNumeric.num2long(pass));
}
rsaGenerate(this, keysize, exp); return this;
@@ -222,18 +222,26 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
final KeyFactory rsaFactory;
try {
rsaFactory = SecurityHelper.getKeyFactory("RSA");
} catch (NoSuchAlgorithmException e) {
}
catch (NoSuchAlgorithmException e) {
throw runtime.newRuntimeError("unsupported key algorithm (RSA)");
} catch (RuntimeException e) {
}
catch (RuntimeException e) {
throw runtime.newRuntimeError("unsupported key algorithm (RSA) " + e);
}
// TODO: ugly NoClassDefFoundError catching for no BC env. How can we remove this?
boolean noClassDef = false;
if ( key == null && ! noClassDef ) { // PEM_read_bio_RSAPrivateKey
try {
key = PEMInputOutput.readPrivateKey(new StringReader(str.toString()), passwd);
key = readPrivateKey(str, 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 ) { // PEM_read_bio_RSAPublicKey
92 changes: 60 additions & 32 deletions src/main/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java
Original file line number Diff line number Diff line change
@@ -205,22 +205,25 @@ public static Object readPEM(final BufferedReader reader, final char[] passwd) t
if ( line.indexOf(BEG_STRING_PUBLIC) != -1 ) {
try {
return readPublicKey(reader,BEF_E+PEM_STRING_PUBLIC);
} catch (Exception e) {
throw new IOException("problem creating public key: " + e.toString(), e);
}
catch (Exception e) {
throw mapReadException("problem creating public key: ", e);
}
}
else if ( line.indexOf(BEG_STRING_DSA) != -1 ) {
try {
return readKeyPair(reader,passwd, "DSA", BEF_E+PEM_STRING_DSA);
} catch (Exception e) {
throw new IOException("problem creating DSA private key: " + e.toString(), e);
}
catch (Exception e) {
throw mapReadException("problem creating DSA private key: ", e);
}
}
else if ( line.indexOf(BEG_STRING_RSA_PUBLIC) != -1 ) {
try {
return readPublicKey(reader,BEF_E+PEM_STRING_RSA_PUBLIC);
} catch (Exception e) {
throw new IOException("problem creating RSA public key: " + e.toString(), e);
}
catch (Exception e) {
throw mapReadException("problem creating RSA public key: ", e);
}
}
else if ( line.indexOf(BEG_STRING_X509_OLD) != -1 ) {
@@ -308,7 +311,8 @@ else if ( line.indexOf(BEG_STRING_X509_REQ) != -1 ) {
* c: PEM_read_PrivateKey + PEM_read_bio_PrivateKey
* CAUTION: KeyPair#getPublic() may be null.
*/
public static KeyPair readPrivateKey(final Reader in, char[] passwd) throws IOException {
public static KeyPair readPrivateKey(final Reader in, char[] passwd)
throws PasswordRequiredException, IOException {
final String BEG_STRING_ECPRIVATEKEY = BEF_G + PEM_STRING_ECPRIVATEKEY;
final String BEG_STRING_PKCS8INF = BEF_G + PEM_STRING_PKCS8INF;
final String BEG_STRING_PKCS8 = BEF_G + PEM_STRING_PKCS8;
@@ -318,15 +322,17 @@ public static KeyPair readPrivateKey(final Reader in, char[] passwd) throws IOEx
if ( line.indexOf(BEG_STRING_RSA) != -1 ) {
try {
return readKeyPair(reader, passwd, "RSA", BEF_E + PEM_STRING_RSA);
} catch (Exception e) {
throw new IOException("problem creating RSA private key: " + e.toString(), e);
}
catch (Exception e) {
throw mapReadException("problem creating RSA private key: ", e);
}
}
else if ( line.indexOf(BEG_STRING_DSA) != -1 ) {
try {
return readKeyPair(reader, passwd, "DSA", BEF_E + PEM_STRING_DSA);
} catch (Exception e) {
throw new IOException("problem creating DSA private key: " + e.toString(), e);
}
catch (Exception e) {
throw mapReadException("problem creating DSA private key: ", e);
}
}
else if ( line.indexOf(BEG_STRING_ECPRIVATEKEY) != -1) { // TODO EC!
@@ -340,7 +346,7 @@ else if ( line.indexOf(BEG_STRING_PKCS8INF) != -1) {
return org.jruby.ext.openssl.impl.PKey.readPrivateKey(((ASN1Object) info.parsePrivateKey()).getEncoded(ASN1Encoding.DER), type);
}
catch (Exception e) {
throw new IOException("problem creating private key: " + e.toString(), e);
throw mapReadException("problem creating private key: ", e);
}
}
else if ( line.indexOf(BEG_STRING_PKCS8) != -1 ) {
@@ -355,14 +361,22 @@ else if ( line.indexOf(BEG_STRING_PKCS8) != -1 ) {
privKey = derivePrivateKeyPBES1(eIn, algId, passwd);
}
return new KeyPair(null, privKey);
} catch (Exception e) {
throw new IOException("problem creating private key: " + e.toString(), e);
}
catch (Exception e) {
throw mapReadException("problem creating private key: ", e);
}
}
}
return null;
}

private static IOException mapReadException(final String message, final Exception ex) {
if ( ex instanceof PasswordRequiredException ) {
return (PasswordRequiredException) ex;
}
return new IOException(message + ex, ex);
}

private static PrivateKey derivePrivateKeyPBES1(EncryptedPrivateKeyInfo eIn, AlgorithmIdentifier algId, char[] password)
throws GeneralSecurityException, IOException {
// From BC's PEMReader
@@ -452,7 +466,7 @@ public static DSAPublicKey readDSAPubKey(Reader in) throws IOException {
return (DSAPublicKey) readPublicKey(reader, "DSA", BEF_E + PEM_STRING_DSA_PUBLIC);
}
catch (Exception e) {
throw new IOException("problem creating DSA public key: " + e.toString(), e);
throw mapReadException("problem creating DSA public key: ", e);
}
}
}
@@ -470,7 +484,7 @@ public static DSAPublicKey readDSAPublicKey(final Reader in, final char[] passwd
return (DSAPublicKey) readPublicKey(reader, "DSA", BEF_E + PEM_STRING_PUBLIC);
}
catch (Exception e) {
throw new IOException("problem creating DSA public key: " + e.toString(), e);
throw mapReadException("problem creating DSA public key: ", e);
}
}
}
@@ -480,15 +494,16 @@ public static DSAPublicKey readDSAPublicKey(final Reader in, final char[] passwd
/*
* c: PEM_read_bio_DSAPrivateKey
*/
public static KeyPair readDSAPrivateKey(final Reader in, final char[] passwd) throws IOException {
public static KeyPair readDSAPrivateKey(final Reader in, final char[] passwd)
throws PasswordRequiredException, IOException {
final BufferedReader reader = makeBuffered(in); String line;
while ( ( line = reader.readLine() ) != null ) {
if ( line.indexOf(BEG_STRING_DSA) != -1 ) {
try {
return readKeyPair(reader, passwd, "DSA", BEF_E + PEM_STRING_DSA);
}
catch (Exception e) {
throw new IOException("problem creating DSA private key: " + e.toString(), e);
throw mapReadException("problem creating DSA private key: ", e);
}
}
}
@@ -505,15 +520,17 @@ public static RSAPublicKey readRSAPubKey(Reader in) throws IOException {
if ( line.indexOf(BEG_STRING_PUBLIC) != -1 ) {
try {
return readRSAPublicKey(reader, BEF_E + PEM_STRING_PUBLIC);
} catch (Exception e) {
throw new IOException("problem creating RSA public key: " + e.toString(), e);
}
catch (Exception e) {
throw mapReadException("problem creating RSA public key: ", e);
}
}
else if ( line.indexOf(BEG_STRING_RSA_PUBLIC) != -1 ) {
try {
return readRSAPublicKey(reader, BEF_E + PEM_STRING_RSA_PUBLIC);
} catch (Exception e) {
throw new IOException("problem creating RSA public key: " + e.toString(), e);
}
catch (Exception e) {
throw mapReadException("problem creating RSA public key: ", e);
}
}
}
@@ -524,23 +541,24 @@ else if ( line.indexOf(BEG_STRING_RSA_PUBLIC) != -1 ) {
* reads an RSA public key encoded in an PKCS#1 RSA structure.
* c: PEM_read_bio_RSAPublicKey
*/
public static RSAPublicKey readRSAPublicKey(Reader in, char[] f) throws IOException {
public static RSAPublicKey readRSAPublicKey(Reader in, char[] f)
throws PasswordRequiredException, IOException {
final BufferedReader reader = makeBuffered(in); String line;
while ( ( line = reader.readLine() ) != null ) {
if ( line.indexOf(BEG_STRING_PUBLIC) != -1 ) {
try {
return (RSAPublicKey) readPublicKey(reader, "RSA", BEF_E + PEM_STRING_PUBLIC);
}
catch (Exception e) {
throw new IOException("problem creating RSA public key: " + e.toString(), e);
throw mapReadException("problem creating RSA public key: ", e);
}
}
else if ( line.indexOf(BEF_G+PEM_STRING_RSA_PUBLIC) != -1 ) {
try {
return (RSAPublicKey) readPublicKey(reader, "RSA", BEF_E + PEM_STRING_RSA_PUBLIC);
}
catch (Exception e) {
throw new IOException("problem creating RSA public key: " + e.toString(), e);
throw mapReadException("problem creating RSA public key: ", e);
}
}
}
@@ -550,15 +568,16 @@ else if ( line.indexOf(BEF_G+PEM_STRING_RSA_PUBLIC) != -1 ) {
/**
* c: PEM_read_bio_RSAPrivateKey
*/
public static KeyPair readRSAPrivateKey(Reader in, char[] f) throws IOException {
public static KeyPair readRSAPrivateKey(Reader in, char[] f)
throws PasswordRequiredException, IOException {
final BufferedReader reader = makeBuffered(in); String line;
while ( ( line = reader.readLine() ) != null ) {
if ( line.indexOf(BEG_STRING_RSA) != -1 ) {
try {
return readKeyPair(reader,f, "RSA", BEF_E + PEM_STRING_RSA);
}
catch (Exception e) {
throw new IOException("problem creating RSA private key: " + e.toString(), e);
throw mapReadException("problem creating RSA private key: ", e);
}
}
}
@@ -1091,7 +1110,8 @@ 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 {
private static KeyPair readKeyPair(BufferedReader in, char[] passwd, String type, String endMarker)
throws PasswordRequiredException, IOException, GeneralSecurityException {
boolean isEncrypted = false;
String dekInfo = null;

@@ -1121,10 +1141,10 @@ else if ( line.contains(endMarker) ) {
return org.jruby.ext.openssl.impl.PKey.readPrivateKey(keyBytes, type);
}

private static byte[] decrypt(byte[] decoded, String dekInfo, char[] passwd) throws IOException, GeneralSecurityException {
if (passwd == null) {
throw new IOException("Password is null, but a password is required");
}
private static byte[] decrypt(byte[] decoded, String dekInfo, char[] passwd)
throws PasswordRequiredException, IOException, GeneralSecurityException {
if ( passwd == null ) throw new PasswordRequiredException();

StringTokenizer tknz = new StringTokenizer(dekInfo, ",");
String algorithm = tknz.nextToken();
byte[] iv = Hex.decode(tknz.nextToken());
@@ -1151,6 +1171,14 @@ private static byte[] decrypt(byte[] decoded, String dekInfo, char[] passwd) thr
return cipher.doFinal(decoded);
}

public static class PasswordRequiredException extends IOException {

PasswordRequiredException() {
super();
}

}

/**
* Reads in a X509Certificate.
*

0 comments on commit 098ad46

Please sign in to comment.