44import java .io .File ;
55import java .io .FileInputStream ;
66import java .io .IOException ;
7+ import java .math .BigInteger ;
8+ import java .nio .charset .StandardCharsets ;
79import java .nio .file .Files ;
810import java .security .KeyFactory ;
911import java .security .KeyStore ;
1921import java .security .spec .InvalidKeySpecException ;
2022import java .security .spec .KeySpec ;
2123import java .security .spec .PKCS8EncodedKeySpec ;
24+ import java .security .spec .RSAPrivateCrtKeySpec ;
2225import java .util .ArrayList ;
26+ import java .util .Base64 ;
2327import java .util .List ;
2428import java .util .concurrent .atomic .AtomicInteger ;
29+ import java .util .regex .Matcher ;
30+ import java .util .regex .Pattern ;
2531
2632import static java .util .Objects .requireNonNull ;
2733
@@ -30,6 +36,9 @@ class KeyStoreBuilder {
3036 private static final String TYPE_CERTIFICATE = "X.509" ;
3137 private static final String ALGORITHM_PRIVATE_KEY = "RSA" ;
3238
39+ private static final Pattern PATTERN_PKCS1_PEM = Pattern .compile ("-----BEGIN RSA PRIVATE KEY-----(.*)-----END RSA PRIVATE KEY-----" );
40+ private static final Pattern PATTERN_PKCS8_PEM = Pattern .compile ("-----BEGIN PRIVATE KEY-----(.*)-----END PRIVATE KEY-----" );
41+
3342 private ProtectionParameter keyStoreProtection ;
3443 private File caCertFile ;
3544 private File privateKeyCertFile ;
@@ -62,17 +71,6 @@ KeyStoreBuilder withPrivateKeyCertFile(File privateKeyCertFile) {
6271 return this ;
6372 }
6473
65- /**
66- * Provide the private key file to use. It must be in {@code *.DER} format. If you have a *.PME private key, you can create it using
67- * {@code openssl}.
68- * <p>
69- * The required command looks like following:<br>
70- * <code>$ openssl pkcs8 -topk8 -nocrypt -in key.pem -inform PEM -out key.der -outform DER</code>
71- * </p>
72- *
73- * @param privateKeyFile private key file in {@code *.DER} format (must not be {@code null}
74- * @return the {@link KeyStore} builder itself
75- */
7674 KeyStoreBuilder withPrivateKeyFile (File privateKeyFile ) {
7775 requireNonNull (privateKeyFile , "'privateKeyFile' must not be null." );
7876 if (!privateKeyFile .exists ()) {
@@ -129,12 +127,56 @@ private List<Certificate> createCertificatesFor(File certFile) {
129127 private PrivateKey createPrivateKeyFor (File privateKeyFile ) {
130128 try {
131129 byte [] bytes = Files .readAllBytes (privateKeyFile .toPath ());
130+ if (privateKeyFile .getName ().endsWith ("pem" )) {
131+ String content = new String (bytes , StandardCharsets .UTF_8 ).replaceAll ("\\ n" , "" );
132+
133+ Matcher pkcs1Matcher = PATTERN_PKCS1_PEM .matcher (content );
134+ if (pkcs1Matcher .find ()) {
135+ return createPrivateKeyFromPemPkcs1 (pkcs1Matcher .group (1 ));
136+ }
137+
138+ Matcher pkcs8Matcher = PATTERN_PKCS8_PEM .matcher (content );
139+ if (pkcs8Matcher .find ()) {
140+ return createPrivateKeyFromPemPkcs8 (pkcs8Matcher .group (1 ));
141+ }
142+
143+ throw new TaskwarriorKeyStoreException ("Could not detect key algorithm for '%s'." , privateKeyFile );
144+ }
132145 return createPrivateKeyFromPkcs8Der (bytes );
133146 } catch (IOException e ) {
134147 throw new TaskwarriorKeyStoreException (e , "Could not read private key of '%s' via input stream." , privateKeyFile );
135148 }
136149 }
137150
151+ @ SuppressWarnings ("sunapi" )
152+ private PrivateKey createPrivateKeyFromPemPkcs1 (String privateKeyContent ) throws IOException {
153+ try {
154+ byte [] bytes = Base64 .getDecoder ().decode (privateKeyContent );
155+
156+ sun .security .util .DerInputStream derReader = new sun .security .util .DerInputStream (bytes );
157+ sun .security .util .DerValue [] seq = derReader .getSequence (0 );
158+ // skip version seq[0];
159+ BigInteger modulus = seq [1 ].getBigInteger ();
160+ BigInteger publicExp = seq [2 ].getBigInteger ();
161+ BigInteger privateExp = seq [3 ].getBigInteger ();
162+ BigInteger prime1 = seq [4 ].getBigInteger ();
163+ BigInteger prime2 = seq [5 ].getBigInteger ();
164+ BigInteger exp1 = seq [6 ].getBigInteger ();
165+ BigInteger exp2 = seq [7 ].getBigInteger ();
166+ BigInteger crtCoef = seq [8 ].getBigInteger ();
167+
168+ RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec (modulus , publicExp , privateExp , prime1 , prime2 , exp1 , exp2 , crtCoef );
169+ return createPrivateKey (privateKeyFile , keySpec );
170+ } catch (Error | Exception e ) {
171+ throw new TaskwarriorKeyStoreException ("Could not use required but proprietary 'sun.security.util' package on this platform." , e );
172+ }
173+ }
174+
175+ private PrivateKey createPrivateKeyFromPemPkcs8 (String privateKeyContent ) {
176+ byte [] bytes = Base64 .getDecoder ().decode (privateKeyContent );
177+ return createPrivateKey (privateKeyFile , new PKCS8EncodedKeySpec (bytes ));
178+ }
179+
138180 private PrivateKey createPrivateKeyFromPkcs8Der (byte [] privateKeyBytes ) {
139181 return createPrivateKey (privateKeyFile , new PKCS8EncodedKeySpec (privateKeyBytes ));
140182 }
0 commit comments