11package de .aaschmid .taskwarrior .client ;
22
33import java .io .BufferedInputStream ;
4+ import java .io .ByteArrayInputStream ;
45import java .io .File ;
56import java .io .FileInputStream ;
67import java .io .IOException ;
7- import java .math . BigInteger ;
8+ import java .io . InputStreamReader ;
89import java .nio .charset .StandardCharsets ;
910import java .nio .file .Files ;
1011import java .security .KeyFactory ;
1920import java .security .cert .CertificateException ;
2021import java .security .cert .CertificateFactory ;
2122import java .security .spec .InvalidKeySpecException ;
22- import java .security .spec .KeySpec ;
2323import java .security .spec .PKCS8EncodedKeySpec ;
24- import java .security .spec .RSAPrivateCrtKeySpec ;
2524import java .util .ArrayList ;
26- import java .util .Base64 ;
2725import java .util .List ;
2826import java .util .concurrent .atomic .AtomicInteger ;
29- import java .util .regex .Matcher ;
30- import java .util .regex .Pattern ;
27+
28+ import org .bouncycastle .asn1 .pkcs .RSAPrivateKey ;
29+ import org .bouncycastle .crypto .params .RSAPrivateCrtKeyParameters ;
30+ import org .bouncycastle .crypto .util .PrivateKeyInfoFactory ;
31+ import org .bouncycastle .openssl .jcajce .JcaPEMKeyConverter ;
32+ import org .bouncycastle .util .io .pem .PemObject ;
33+ import org .bouncycastle .util .io .pem .PemReader ;
3134
3235import static java .util .Objects .requireNonNull ;
3336
3437class KeyStoreBuilder {
3538
36- private static final String TYPE_CERTIFICATE = "X.509" ;
37- private static final String ALGORITHM_PRIVATE_KEY = "RSA" ;
38-
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-----" );
39+ private static final String CERTIFICATE_TYPE = "X.509" ;
40+ private static final String KEY_ALGORITHM_RSA = "RSA" ;
41+ private static final String PEM_TYPE_PKCS1 = "RSA PRIVATE KEY" ;
42+ private static final String PEM_TYPE_PKCS8 = "PRIVATE KEY" ;
4143
4244 private ProtectionParameter keyStoreProtection ;
4345 private File caCertFile ;
@@ -71,6 +73,10 @@ KeyStoreBuilder withPrivateKeyCertFile(File privateKeyCertFile) {
7173 return this ;
7274 }
7375
76+ /**
77+ * Provide the non-null private key file to use. Supported are PKCS#1 and PKCS#8 keys in {@code *.PEM} format as well as PKCS#8 keys in
78+ * {@code *.DER} format.
79+ */
7480 KeyStoreBuilder withPrivateKeyFile (File privateKeyFile ) {
7581 requireNonNull (privateKeyFile , "'privateKeyFile' must not be null." );
7682 if (!privateKeyFile .exists ()) {
@@ -112,7 +118,7 @@ KeyStore build() {
112118 private List <Certificate > createCertificatesFor (File certFile ) {
113119 List <Certificate > result = new ArrayList <>();
114120 try (BufferedInputStream bis = new BufferedInputStream (new FileInputStream (certFile ))) {
115- CertificateFactory cf = CertificateFactory .getInstance (TYPE_CERTIFICATE );
121+ CertificateFactory cf = CertificateFactory .getInstance (CERTIFICATE_TYPE );
116122 while (bis .available () > 0 ) {
117123 result .add (cf .generateCertificate (bis ));
118124 }
@@ -128,67 +134,51 @@ private PrivateKey createPrivateKeyFor(File privateKeyFile) {
128134 try {
129135 byte [] bytes = Files .readAllBytes (privateKeyFile .toPath ());
130136 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 ));
137+ PemReader pemReader = new PemReader (new InputStreamReader (new ByteArrayInputStream (bytes ), StandardCharsets .UTF_8 ));
138+ PemObject privateKeyObject = pemReader .readPemObject ();
139+
140+ switch (privateKeyObject .getType ()) {
141+ case PEM_TYPE_PKCS1 :
142+ return createPrivateKeyForPkcs1 (privateKeyObject .getContent ());
143+ case PEM_TYPE_PKCS8 :
144+ return createPrivateKeyForPkcs8 (privateKeyObject .getContent ());
145+ default :
146+ throw new TaskwarriorKeyStoreException ("Unsupported key algorithm '%s'." , privateKeyObject .getType ());
136147 }
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 );
144148 }
145- return createPrivateKeyFromPkcs8Der (bytes );
149+ return createPrivateKeyForPkcs8 (bytes );
146150 } catch (IOException e ) {
147151 throw new TaskwarriorKeyStoreException (e , "Could not read private key of '%s' via input stream." , privateKeyFile );
148152 }
149153 }
150154
151- @ SuppressWarnings ("sunapi" )
152- private PrivateKey createPrivateKeyFromPemPkcs1 (String privateKeyContent ) throws IOException {
155+ private PrivateKey createPrivateKeyForPkcs1 (byte [] privateKeyBytes ) {
156+ RSAPrivateKey rsa = RSAPrivateKey .getInstance (privateKeyBytes );
157+ RSAPrivateCrtKeyParameters keyParameters = new RSAPrivateCrtKeyParameters (
158+ rsa .getModulus (),
159+ rsa .getPublicExponent (),
160+ rsa .getPrivateExponent (),
161+ rsa .getPrime1 (),
162+ rsa .getPrime2 (),
163+ rsa .getExponent1 (),
164+ rsa .getExponent2 (),
165+ rsa .getCoefficient ());
166+
153167 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 );
168+ return new JcaPEMKeyConverter ().getPrivateKey (PrivateKeyInfoFactory .createPrivateKeyInfo (keyParameters ));
169+ } catch (IOException e ) {
170+ throw new TaskwarriorKeyStoreException (e , "Failed to encode PKCS#1 private key of '%s'." , privateKeyFile );
172171 }
173172 }
174173
175- private PrivateKey createPrivateKeyFromPemPkcs8 (String privateKeyContent ) {
176- byte [] bytes = Base64 .getDecoder ().decode (privateKeyContent );
177- return createPrivateKey (privateKeyFile , new PKCS8EncodedKeySpec (bytes ));
178- }
179-
180- private PrivateKey createPrivateKeyFromPkcs8Der (byte [] privateKeyBytes ) {
181- return createPrivateKey (privateKeyFile , new PKCS8EncodedKeySpec (privateKeyBytes ));
182- }
183-
184- private PrivateKey createPrivateKey (File privateKeyFile , KeySpec keySpec ) {
174+ private PrivateKey createPrivateKeyForPkcs8 (byte [] privateKeyBytes ) {
185175 try {
186- KeyFactory keyFactory = KeyFactory .getInstance (ALGORITHM_PRIVATE_KEY );
187- return keyFactory .generatePrivate (keySpec );
176+ KeyFactory keyFactory = KeyFactory .getInstance (KEY_ALGORITHM_RSA );
177+ return keyFactory .generatePrivate (new PKCS8EncodedKeySpec ( privateKeyBytes ) );
188178 } catch (NoSuchAlgorithmException e ) {
189- throw new TaskwarriorKeyStoreException (e , "Key factory could not be initialized for algorithm '%s'." , ALGORITHM_PRIVATE_KEY );
179+ throw new TaskwarriorKeyStoreException (e , "Key factory could not be initialized for algorithm '%s'." , KEY_ALGORITHM_RSA );
190180 } catch (InvalidKeySpecException e ) {
191- throw new TaskwarriorKeyStoreException (e , "Could not generate private key for '%s'." , privateKeyFile );
181+ throw new TaskwarriorKeyStoreException (e , "Invalid key spec for %s private key in '%s'." , KEY_ALGORITHM_RSA , privateKeyFile );
192182 }
193183 }
194184}
0 commit comments