Skip to content

Commit 098ad46

Browse files
committed
basic support for prompting for PEM password (currently working for RSA/DSA priv.key)
1 parent b25de83 commit 098ad46

File tree

5 files changed

+123
-42
lines changed

5 files changed

+123
-42
lines changed

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.io.ByteArrayInputStream;
3131
import java.io.IOException;
3232
import java.io.InputStreamReader;
33+
import java.io.StringReader;
3334
import java.math.BigInteger;
3435
import java.security.GeneralSecurityException;
3536
import java.security.InvalidKeyException;
@@ -350,6 +351,34 @@ protected static char[] password(final IRubyObject pass) {
350351
return null;
351352
}
352353

354+
protected static char[] passwordPrompt(final ThreadContext context) {
355+
return passwordPrompt(context, "Enter PEM pass phrase:");
356+
}
357+
358+
protected static char[] passwordPrompt(final ThreadContext context, final String prompt) {
359+
final RubyModule Kernel = context.runtime.getKernel();
360+
// NOTE: just a fast and simple print && gets - hopefully better than nothing!
361+
Kernel.callMethod("print", context.runtime.newString(prompt));
362+
final RubyString gets = Kernel.callMethod(context, "gets").convertToString();
363+
gets.chomp_bang(context);
364+
return gets.toString().toCharArray();
365+
}
366+
367+
protected static boolean ttySTDIN(final ThreadContext context) {
368+
final IRubyObject stdin = context.runtime.getGlobalVariables().get("$stdin");
369+
if ( stdin == null || stdin.isNil() ) return false;
370+
try {
371+
final IRubyObject tty = stdin.callMethod(context, "tty?");
372+
return ! tty.isNil() && ! ( tty == context.runtime.getFalse() );
373+
}
374+
catch (RaiseException ex) { return false; }
375+
}
376+
377+
static Object readPrivateKey(final RubyString str, final char[] passwd)
378+
throws PEMInputOutput.PasswordRequiredException, IOException {
379+
return PEMInputOutput.readPrivateKey(new StringReader(str.toString()), passwd);
380+
}
381+
353382
protected static RubyString readInitArg(final ThreadContext context, IRubyObject arg) {
354383
return StringHelper.readPossibleDERInput(context, arg);
355384
}

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,11 @@ public synchronized IRubyObject initialize(final ThreadContext context, final IR
144144
}
145145
this.dh_p = spec.getP();
146146
this.dh_g = spec.getG();
147-
} catch (NoClassDefFoundError e) {
147+
}
148+
catch (NoClassDefFoundError e) {
148149
throw newDHError(runtime, bcExceptionMessage(e));
149-
} catch (IOException e) {
150+
}
151+
catch (IOException e) {
150152
throw runtime.newIOErrorFromException(e);
151153
}
152154
} else {
@@ -156,7 +158,8 @@ public synchronized IRubyObject initialize(final ThreadContext context, final IR
156158
BigInteger p;
157159
try {
158160
p = generateP(bits, gval);
159-
} catch(IllegalArgumentException e) {
161+
}
162+
catch(IllegalArgumentException e) {
160163
throw runtime.newArgumentError(e.getMessage());
161164
}
162165
BigInteger g = BigInteger.valueOf(gval);

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,18 +181,26 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
181181
final KeyFactory dsaFactory;
182182
try {
183183
dsaFactory = SecurityHelper.getKeyFactory("DSA");
184-
} catch (NoSuchAlgorithmException e) {
184+
}
185+
catch (NoSuchAlgorithmException e) {
185186
throw runtime.newRuntimeError("unsupported key algorithm (DSA)");
186-
} catch (RuntimeException e) {
187+
}
188+
catch (RuntimeException e) {
187189
throw runtime.newRuntimeError("unsupported key algorithm (DSA) " + e);
188190
}
189191
// TODO: ugly NoClassDefFoundError catching for no BC env. How can we remove this?
190192
boolean noClassDef = false;
191193
if ( key == null && ! noClassDef ) { // PEM_read_bio_DSAPrivateKey
192194
try {
193-
key = PEMInputOutput.readDSAPrivateKey(new StringReader(str.toString()), passwd);
195+
key = readPrivateKey(str, passwd);
194196
}
195197
catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); }
198+
catch (PEMInputOutput.PasswordRequiredException retry) {
199+
if ( ttySTDIN(context) ) {
200+
try { key = readPrivateKey(str, passwordPrompt(context)); }
201+
catch (Exception e) { debugStackTrace(runtime, e); }
202+
}
203+
}
196204
catch (Exception e) { debugStackTrace(runtime, e); }
197205
}
198206
if ( key == null && ! noClassDef ) { // PEM_read_bio_DSAPublicKey
@@ -263,6 +271,11 @@ else if ( key instanceof DSAPublicKey ) {
263271
return this;
264272
}
265273

274+
//private static Object readPrivateKey(final RubyString str, final char[] passwd)
275+
// throws PEMInputOutput.PasswordRequiredException, IOException {
276+
// return PEMInputOutput.readDSAPrivateKey(new StringReader(str.toString()), passwd);
277+
//}
278+
266279
@JRubyMethod(name = "public?")
267280
public RubyBoolean public_p() {
268281
return publicKey != null ? getRuntime().getTrue() : getRuntime().getFalse();

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
209209
if ( arg instanceof RubyFixnum ) {
210210
int keysize = RubyNumeric.fix2int((RubyFixnum) arg);
211211
BigInteger exp = RSAKeyGenParameterSpec.F4;
212-
if (null != pass && !pass.isNil()) {
212+
if ( pass != null && ! pass.isNil() ) {
213213
exp = BigInteger.valueOf(RubyNumeric.num2long(pass));
214214
}
215215
rsaGenerate(this, keysize, exp); return this;
@@ -222,18 +222,26 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
222222
final KeyFactory rsaFactory;
223223
try {
224224
rsaFactory = SecurityHelper.getKeyFactory("RSA");
225-
} catch (NoSuchAlgorithmException e) {
225+
}
226+
catch (NoSuchAlgorithmException e) {
226227
throw runtime.newRuntimeError("unsupported key algorithm (RSA)");
227-
} catch (RuntimeException e) {
228+
}
229+
catch (RuntimeException e) {
228230
throw runtime.newRuntimeError("unsupported key algorithm (RSA) " + e);
229231
}
230232
// TODO: ugly NoClassDefFoundError catching for no BC env. How can we remove this?
231233
boolean noClassDef = false;
232234
if ( key == null && ! noClassDef ) { // PEM_read_bio_RSAPrivateKey
233235
try {
234-
key = PEMInputOutput.readPrivateKey(new StringReader(str.toString()), passwd);
236+
key = readPrivateKey(str, passwd);
235237
}
236238
catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); }
239+
catch (PEMInputOutput.PasswordRequiredException retry) {
240+
if ( ttySTDIN(context) ) {
241+
try { key = readPrivateKey(str, passwordPrompt(context)); }
242+
catch (Exception e) { debugStackTrace(runtime, e); }
243+
}
244+
}
237245
catch (Exception e) { debugStackTrace(runtime, e); }
238246
}
239247
if ( key == null && ! noClassDef ) { // PEM_read_bio_RSAPublicKey

src/main/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -205,22 +205,25 @@ public static Object readPEM(final BufferedReader reader, final char[] passwd) t
205205
if ( line.indexOf(BEG_STRING_PUBLIC) != -1 ) {
206206
try {
207207
return readPublicKey(reader,BEF_E+PEM_STRING_PUBLIC);
208-
} catch (Exception e) {
209-
throw new IOException("problem creating public key: " + e.toString(), e);
208+
}
209+
catch (Exception e) {
210+
throw mapReadException("problem creating public key: ", e);
210211
}
211212
}
212213
else if ( line.indexOf(BEG_STRING_DSA) != -1 ) {
213214
try {
214215
return readKeyPair(reader,passwd, "DSA", BEF_E+PEM_STRING_DSA);
215-
} catch (Exception e) {
216-
throw new IOException("problem creating DSA private key: " + e.toString(), e);
216+
}
217+
catch (Exception e) {
218+
throw mapReadException("problem creating DSA private key: ", e);
217219
}
218220
}
219221
else if ( line.indexOf(BEG_STRING_RSA_PUBLIC) != -1 ) {
220222
try {
221223
return readPublicKey(reader,BEF_E+PEM_STRING_RSA_PUBLIC);
222-
} catch (Exception e) {
223-
throw new IOException("problem creating RSA public key: " + e.toString(), e);
224+
}
225+
catch (Exception e) {
226+
throw mapReadException("problem creating RSA public key: ", e);
224227
}
225228
}
226229
else if ( line.indexOf(BEG_STRING_X509_OLD) != -1 ) {
@@ -308,7 +311,8 @@ else if ( line.indexOf(BEG_STRING_X509_REQ) != -1 ) {
308311
* c: PEM_read_PrivateKey + PEM_read_bio_PrivateKey
309312
* CAUTION: KeyPair#getPublic() may be null.
310313
*/
311-
public static KeyPair readPrivateKey(final Reader in, char[] passwd) throws IOException {
314+
public static KeyPair readPrivateKey(final Reader in, char[] passwd)
315+
throws PasswordRequiredException, IOException {
312316
final String BEG_STRING_ECPRIVATEKEY = BEF_G + PEM_STRING_ECPRIVATEKEY;
313317
final String BEG_STRING_PKCS8INF = BEF_G + PEM_STRING_PKCS8INF;
314318
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
318322
if ( line.indexOf(BEG_STRING_RSA) != -1 ) {
319323
try {
320324
return readKeyPair(reader, passwd, "RSA", BEF_E + PEM_STRING_RSA);
321-
} catch (Exception e) {
322-
throw new IOException("problem creating RSA private key: " + e.toString(), e);
325+
}
326+
catch (Exception e) {
327+
throw mapReadException("problem creating RSA private key: ", e);
323328
}
324329
}
325330
else if ( line.indexOf(BEG_STRING_DSA) != -1 ) {
326331
try {
327332
return readKeyPair(reader, passwd, "DSA", BEF_E + PEM_STRING_DSA);
328-
} catch (Exception e) {
329-
throw new IOException("problem creating DSA private key: " + e.toString(), e);
333+
}
334+
catch (Exception e) {
335+
throw mapReadException("problem creating DSA private key: ", e);
330336
}
331337
}
332338
else if ( line.indexOf(BEG_STRING_ECPRIVATEKEY) != -1) { // TODO EC!
@@ -340,7 +346,7 @@ else if ( line.indexOf(BEG_STRING_PKCS8INF) != -1) {
340346
return org.jruby.ext.openssl.impl.PKey.readPrivateKey(((ASN1Object) info.parsePrivateKey()).getEncoded(ASN1Encoding.DER), type);
341347
}
342348
catch (Exception e) {
343-
throw new IOException("problem creating private key: " + e.toString(), e);
349+
throw mapReadException("problem creating private key: ", e);
344350
}
345351
}
346352
else if ( line.indexOf(BEG_STRING_PKCS8) != -1 ) {
@@ -355,14 +361,22 @@ else if ( line.indexOf(BEG_STRING_PKCS8) != -1 ) {
355361
privKey = derivePrivateKeyPBES1(eIn, algId, passwd);
356362
}
357363
return new KeyPair(null, privKey);
358-
} catch (Exception e) {
359-
throw new IOException("problem creating private key: " + e.toString(), e);
364+
}
365+
catch (Exception e) {
366+
throw mapReadException("problem creating private key: ", e);
360367
}
361368
}
362369
}
363370
return null;
364371
}
365372

373+
private static IOException mapReadException(final String message, final Exception ex) {
374+
if ( ex instanceof PasswordRequiredException ) {
375+
return (PasswordRequiredException) ex;
376+
}
377+
return new IOException(message + ex, ex);
378+
}
379+
366380
private static PrivateKey derivePrivateKeyPBES1(EncryptedPrivateKeyInfo eIn, AlgorithmIdentifier algId, char[] password)
367381
throws GeneralSecurityException, IOException {
368382
// From BC's PEMReader
@@ -452,7 +466,7 @@ public static DSAPublicKey readDSAPubKey(Reader in) throws IOException {
452466
return (DSAPublicKey) readPublicKey(reader, "DSA", BEF_E + PEM_STRING_DSA_PUBLIC);
453467
}
454468
catch (Exception e) {
455-
throw new IOException("problem creating DSA public key: " + e.toString(), e);
469+
throw mapReadException("problem creating DSA public key: ", e);
456470
}
457471
}
458472
}
@@ -470,7 +484,7 @@ public static DSAPublicKey readDSAPublicKey(final Reader in, final char[] passwd
470484
return (DSAPublicKey) readPublicKey(reader, "DSA", BEF_E + PEM_STRING_PUBLIC);
471485
}
472486
catch (Exception e) {
473-
throw new IOException("problem creating DSA public key: " + e.toString(), e);
487+
throw mapReadException("problem creating DSA public key: ", e);
474488
}
475489
}
476490
}
@@ -480,15 +494,16 @@ public static DSAPublicKey readDSAPublicKey(final Reader in, final char[] passwd
480494
/*
481495
* c: PEM_read_bio_DSAPrivateKey
482496
*/
483-
public static KeyPair readDSAPrivateKey(final Reader in, final char[] passwd) throws IOException {
497+
public static KeyPair readDSAPrivateKey(final Reader in, final char[] passwd)
498+
throws PasswordRequiredException, IOException {
484499
final BufferedReader reader = makeBuffered(in); String line;
485500
while ( ( line = reader.readLine() ) != null ) {
486501
if ( line.indexOf(BEG_STRING_DSA) != -1 ) {
487502
try {
488503
return readKeyPair(reader, passwd, "DSA", BEF_E + PEM_STRING_DSA);
489504
}
490505
catch (Exception e) {
491-
throw new IOException("problem creating DSA private key: " + e.toString(), e);
506+
throw mapReadException("problem creating DSA private key: ", e);
492507
}
493508
}
494509
}
@@ -505,15 +520,17 @@ public static RSAPublicKey readRSAPubKey(Reader in) throws IOException {
505520
if ( line.indexOf(BEG_STRING_PUBLIC) != -1 ) {
506521
try {
507522
return readRSAPublicKey(reader, BEF_E + PEM_STRING_PUBLIC);
508-
} catch (Exception e) {
509-
throw new IOException("problem creating RSA public key: " + e.toString(), e);
523+
}
524+
catch (Exception e) {
525+
throw mapReadException("problem creating RSA public key: ", e);
510526
}
511527
}
512528
else if ( line.indexOf(BEG_STRING_RSA_PUBLIC) != -1 ) {
513529
try {
514530
return readRSAPublicKey(reader, BEF_E + PEM_STRING_RSA_PUBLIC);
515-
} catch (Exception e) {
516-
throw new IOException("problem creating RSA public key: " + e.toString(), e);
531+
}
532+
catch (Exception e) {
533+
throw mapReadException("problem creating RSA public key: ", e);
517534
}
518535
}
519536
}
@@ -524,23 +541,24 @@ else if ( line.indexOf(BEG_STRING_RSA_PUBLIC) != -1 ) {
524541
* reads an RSA public key encoded in an PKCS#1 RSA structure.
525542
* c: PEM_read_bio_RSAPublicKey
526543
*/
527-
public static RSAPublicKey readRSAPublicKey(Reader in, char[] f) throws IOException {
544+
public static RSAPublicKey readRSAPublicKey(Reader in, char[] f)
545+
throws PasswordRequiredException, IOException {
528546
final BufferedReader reader = makeBuffered(in); String line;
529547
while ( ( line = reader.readLine() ) != null ) {
530548
if ( line.indexOf(BEG_STRING_PUBLIC) != -1 ) {
531549
try {
532550
return (RSAPublicKey) readPublicKey(reader, "RSA", BEF_E + PEM_STRING_PUBLIC);
533551
}
534552
catch (Exception e) {
535-
throw new IOException("problem creating RSA public key: " + e.toString(), e);
553+
throw mapReadException("problem creating RSA public key: ", e);
536554
}
537555
}
538556
else if ( line.indexOf(BEF_G+PEM_STRING_RSA_PUBLIC) != -1 ) {
539557
try {
540558
return (RSAPublicKey) readPublicKey(reader, "RSA", BEF_E + PEM_STRING_RSA_PUBLIC);
541559
}
542560
catch (Exception e) {
543-
throw new IOException("problem creating RSA public key: " + e.toString(), e);
561+
throw mapReadException("problem creating RSA public key: ", e);
544562
}
545563
}
546564
}
@@ -550,15 +568,16 @@ else if ( line.indexOf(BEF_G+PEM_STRING_RSA_PUBLIC) != -1 ) {
550568
/**
551569
* c: PEM_read_bio_RSAPrivateKey
552570
*/
553-
public static KeyPair readRSAPrivateKey(Reader in, char[] f) throws IOException {
571+
public static KeyPair readRSAPrivateKey(Reader in, char[] f)
572+
throws PasswordRequiredException, IOException {
554573
final BufferedReader reader = makeBuffered(in); String line;
555574
while ( ( line = reader.readLine() ) != null ) {
556575
if ( line.indexOf(BEG_STRING_RSA) != -1 ) {
557576
try {
558577
return readKeyPair(reader,f, "RSA", BEF_E + PEM_STRING_RSA);
559578
}
560579
catch (Exception e) {
561-
throw new IOException("problem creating RSA private key: " + e.toString(), e);
580+
throw mapReadException("problem creating RSA private key: ", e);
562581
}
563582
}
564583
}
@@ -1091,7 +1110,8 @@ private static PublicKey readPublicKey(BufferedReader in, String endMarker) thro
10911110
/**
10921111
* Read a Key Pair
10931112
*/
1094-
private static KeyPair readKeyPair(BufferedReader in, char[] passwd, String type, String endMarker) throws Exception {
1113+
private static KeyPair readKeyPair(BufferedReader in, char[] passwd, String type, String endMarker)
1114+
throws PasswordRequiredException, IOException, GeneralSecurityException {
10951115
boolean isEncrypted = false;
10961116
String dekInfo = null;
10971117

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

1124-
private static byte[] decrypt(byte[] decoded, String dekInfo, char[] passwd) throws IOException, GeneralSecurityException {
1125-
if (passwd == null) {
1126-
throw new IOException("Password is null, but a password is required");
1127-
}
1144+
private static byte[] decrypt(byte[] decoded, String dekInfo, char[] passwd)
1145+
throws PasswordRequiredException, IOException, GeneralSecurityException {
1146+
if ( passwd == null ) throw new PasswordRequiredException();
1147+
11281148
StringTokenizer tknz = new StringTokenizer(dekInfo, ",");
11291149
String algorithm = tknz.nextToken();
11301150
byte[] iv = Hex.decode(tknz.nextToken());
@@ -1151,6 +1171,14 @@ private static byte[] decrypt(byte[] decoded, String dekInfo, char[] passwd) thr
11511171
return cipher.doFinal(decoded);
11521172
}
11531173

1174+
public static class PasswordRequiredException extends IOException {
1175+
1176+
PasswordRequiredException() {
1177+
super();
1178+
}
1179+
1180+
}
1181+
11541182
/**
11551183
* Reads in a X509Certificate.
11561184
*

0 commit comments

Comments
 (0)