1+ /*
2+ Copyright (c) 2010, NHIN Direct Project
3+ All rights reserved.
4+
5+ Authors:
6+ Greg Meyer gm2552@cerner.com
7+
8+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
9+
10+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
11+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
12+ in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org).
13+ nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
14+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
16+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
17+ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
18+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
19+ THE POSSIBILITY OF SUCH DAMAGE.
20+ */
21+
22+ package org .nhindirect .stagent .cert .tools ;
23+
24+ ///CLOVER:OFF
25+ import java .io .ByteArrayInputStream ;
26+ import java .io .File ;
27+ import java .io .FileOutputStream ;
28+ import java .security .Key ;
29+ import java .security .KeyFactory ;
30+ import java .security .KeyStore ;
31+ import java .security .PrivateKey ;
32+ import java .security .cert .X509Certificate ;
33+ import java .security .spec .PKCS8EncodedKeySpec ;
34+ import java .util .Enumeration ;
35+
36+ import javax .crypto .EncryptedPrivateKeyInfo ;
37+ import javax .crypto .SecretKey ;
38+ import javax .crypto .SecretKeyFactory ;
39+ import javax .crypto .spec .PBEKeySpec ;
40+
41+ import org .apache .commons .io .FileUtils ;
42+ import org .apache .commons .io .IOUtils ;
43+ import org .nhindirect .common .crypto .CryptoExtensions ;
44+ import org .nhindirect .stagent .NHINDException ;
45+ import org .nhindirect .stagent .cert .X509CertificateEx ;
46+
47+ /**
48+ * Application class for removing password and private key passphrase protection from PKCS12 files for importing into the Direct Project
49+ * configuration UI.
50+ *
51+ * @author Greg Meyer
52+ */
53+ public class ChangeP12Passphrase
54+ {
55+ private static File p12File ;
56+ private static String filePassPhrase = "" ;
57+ private static String keyPassPhrase = "" ;
58+ private static File createFile ;
59+ private static String newPassPhrase = "" ;
60+
61+ /*
62+ * Load BC the JCS provider.
63+ */
64+ static
65+ {
66+ CryptoExtensions .registerJCEProviders ();
67+ }
68+
69+ /**
70+ * Main entry point when running as an application. Use the -help option for usage.
71+ * @param argv Application arguments.
72+ */
73+ public static void main (String [] argv )
74+ {
75+ if (argv .length == 0 )
76+ {
77+ System .err .println ("Invalid number of arguments: can't have 0 arguments." );
78+ printUsage ();
79+ System .exit (-1 );
80+ }
81+
82+ // Check parameters
83+ for (int i = 0 ; i < argv .length ; i ++)
84+ {
85+ String arg = argv [i ];
86+
87+ // Options
88+ if (!arg .startsWith ("-" ))
89+ {
90+ System .err .println ("Error: Unexpected argument [" + arg + "]\n " );
91+ printUsage ();
92+ System .exit (-1 );
93+ }
94+ else if (arg .equalsIgnoreCase ("-p12" ))
95+ {
96+ if (i == argv .length - 1 || argv [i + 1 ].startsWith ("-" ))
97+ {
98+ System .err .println ("Error: p12 file name." );
99+ System .exit (-1 );
100+ }
101+
102+ p12File = new File (argv [++i ]);
103+
104+ }
105+ else if (arg .equals ("-filePass" ))
106+ {
107+ if (i == argv .length - 1 || argv [i + 1 ].startsWith ("-" ))
108+ {
109+ System .err .println ("Error: Missing p12 file passphrase." );
110+ System .exit (-1 );
111+ }
112+ filePassPhrase = argv [++i ];
113+ }
114+ else if (arg .equals ("-keyPass" ))
115+ {
116+ if (i == argv .length - 1 || argv [i + 1 ].startsWith ("-" ))
117+ {
118+ System .err .println ("Error: Missing private key passphrase." );
119+ System .exit (-1 );
120+ }
121+ keyPassPhrase = argv [++i ];
122+ }
123+ else if (arg .equals ("-out" ))
124+ {
125+ if (i == argv .length - 1 || argv [i + 1 ].startsWith ("-" ))
126+ {
127+ System .err .println ("Error: Missing output file." );
128+ System .exit (-1 );
129+ }
130+ createFile = new File (argv [++i ]);
131+ }
132+ else if (arg .equals ("-outPassword" ))
133+ {
134+ if (i == argv .length - 1 || argv [i + 1 ].startsWith ("-" ))
135+ {
136+ System .err .println ("Error: Missing output password." );
137+ System .exit (-1 );
138+ }
139+ newPassPhrase = argv [++i ];
140+ }
141+ else if (arg .equals ("-help" ))
142+ {
143+ printUsage ();
144+ System .exit (-1 );
145+ }
146+ else
147+ {
148+ System .err .println ("Error: Unknown argument " + arg + "\n " );
149+ printUsage ();
150+ System .exit (-1 );
151+ }
152+ }
153+
154+ if (validateParameters ())
155+ {
156+ stripP12File ();
157+ }
158+ System .exit (0 );
159+ }
160+
161+ /*
162+ * Main strip operation of removing the password and passphrase and creating a new p12 file.
163+ */
164+ @ SuppressWarnings ("deprecation" )
165+ private static void stripP12File ()
166+ {
167+ FileOutputStream outStr = null ;
168+ try
169+ {
170+ byte [] p12Data = loadFileData (p12File );
171+
172+ if (p12Data != null )
173+ {
174+ X509CertificateEx p12Cert = certFromData (p12Data );
175+ if (p12Cert == null )
176+ return ;
177+
178+ File outFile = getPKCS12OutFile ();
179+
180+
181+ KeyStore localKeyStore = KeyStore .getInstance ("PKCS12" , CryptoExtensions .getJCEProviderName ());
182+ localKeyStore .load (null , null );
183+
184+
185+ KeyFactory kf = KeyFactory .getInstance ("RSA" , CryptoExtensions .getJCEProviderName ());
186+ PKCS8EncodedKeySpec keysp = null ;
187+ if (keyPassPhrase != null && !keyPassPhrase .isEmpty ())
188+ {
189+ byte [] keyData = p12Cert .getPrivateKey ().getEncoded ();
190+ EncryptedPrivateKeyInfo encInfo = new EncryptedPrivateKeyInfo (keyData );
191+ PBEKeySpec keySpec = new PBEKeySpec (keyPassPhrase .toCharArray ());
192+ String alg = encInfo .getAlgName ();
193+
194+ SecretKeyFactory secFactory = SecretKeyFactory .getInstance (alg , CryptoExtensions .getJCEProviderName ());
195+ SecretKey secKey = secFactory .generateSecret (keySpec );
196+ keysp = encInfo .getKeySpec (secKey , CryptoExtensions .getJCEProviderName ());
197+ }
198+ else
199+ {
200+ keysp = new PKCS8EncodedKeySpec ( p12Cert .getPrivateKey ().getEncoded () );
201+ }
202+
203+ Key newPrivKey = kf .generatePrivate (keysp );
204+
205+ localKeyStore .setKeyEntry ("privCert" , newPrivKey , newPassPhrase .toCharArray (), new java .security .cert .Certificate [] {p12Cert });
206+
207+
208+ outStr = new FileOutputStream (outFile );
209+ localKeyStore .store (outStr , newPassPhrase .toCharArray ());
210+
211+ System .out .println ("Created pcks12 file " + createFile .getAbsolutePath ());
212+ }
213+ }
214+ catch (Exception e )
215+ {
216+ System .out .println ("Could not create p12 file " + e .getMessage ());
217+ }
218+ finally
219+ {
220+ IOUtils .closeQuietly (outStr );
221+ }
222+ }
223+
224+ /*
225+ * Valid program parameters
226+ */
227+ private static boolean validateParameters ()
228+ {
229+
230+ if (p12File == null )
231+ {
232+ System .out .println ("Missing input p12 file name" );
233+ return false ;
234+ }
235+ if (!p12File .exists ())
236+ {
237+ System .out .println ("P12 file " + p12File .getAbsolutePath () + " does not exist." );
238+ return false ;
239+ }
240+
241+ return true ;
242+ }
243+
244+ /*
245+ * Loads the raw data from the provided file to a byte array.
246+ */
247+ private static byte [] loadFileData (File file ) throws Exception
248+ {
249+ return FileUtils .readFileToByteArray (file );
250+ }
251+
252+ /*
253+ * Creates the output file descriptor and creates the new file on the file system.
254+ */
255+ private static File getPKCS12OutFile () throws Exception
256+ {
257+ if (createFile == null )
258+ {
259+
260+ String fileName = p12File .getName ();
261+
262+ int index = fileName .lastIndexOf ("." );
263+ if (index > -1 )
264+ fileName = fileName .substring (0 , index );
265+
266+ fileName += "_newpass.p12" ;
267+ createFile = new File (fileName );
268+ }
269+
270+ if (createFile .exists ())
271+ createFile .delete ();
272+
273+ createFile .createNewFile ();
274+
275+ return createFile ;
276+ }
277+
278+ /*
279+ * Load the exiting p12 file using the provided password and private key passphrase.
280+ */
281+ @ SuppressWarnings ("deprecation" )
282+ private static X509CertificateEx certFromData (byte [] data )
283+ {
284+ X509CertificateEx retVal = null ;
285+ try
286+ {
287+ ByteArrayInputStream bais = new ByteArrayInputStream (data );
288+
289+ // lets try this a as a PKCS12 data stream first
290+ try
291+ {
292+ KeyStore localKeyStore = KeyStore .getInstance ("PKCS12" , CryptoExtensions .getJCEProviderName ());
293+
294+ localKeyStore .load (bais , filePassPhrase .toCharArray ());
295+ Enumeration <String > aliases = localKeyStore .aliases ();
296+
297+
298+ // we are really expecting only one alias
299+ if (aliases .hasMoreElements ())
300+ {
301+ String alias = aliases .nextElement ();
302+ X509Certificate cert = (X509Certificate )localKeyStore .getCertificate (alias );
303+
304+ // check if there is private key
305+ Key key = localKeyStore .getKey (alias , keyPassPhrase .toCharArray ());
306+ if (key != null && key instanceof PrivateKey )
307+ {
308+ retVal = X509CertificateEx .fromX509Certificate (cert , (PrivateKey )key );
309+ }
310+
311+ }
312+ }
313+ catch (Exception e )
314+ {
315+ // must not be a PKCS12 stream, go on to next step
316+ System .out .println ("Error decoding p12 input file: " + e .getMessage ());
317+ }
318+
319+ IOUtils .closeQuietly (bais );
320+ }
321+ catch (Exception e )
322+ {
323+ throw new NHINDException ("Data cannot be converted to a valid X.509 Certificate" , e );
324+ }
325+
326+ return retVal ;
327+ }
328+
329+ /*
330+ * Print program usage.
331+ */
332+ private static void printUsage ()
333+ {
334+ StringBuffer use = new StringBuffer ();
335+ use .append ("Usage:\n " );
336+ use .append ("java StripP12Passphrase (options)...\n \n " );
337+ use .append ("options:\n " );
338+ use .append ("-p12 p12 File P12 formatted file to strip the passphrase from.\n " );
339+ use .append ("\n " );
340+ use .append ("-filePass File passphrase Optional file passphrase protecting the p12 file.\n " );
341+ use .append (" Default: \" \" \n \n " );
342+ use .append ("-keyPass Key passphrase Optional private key passphrase protecting the internal private key.\n " );
343+ use .append (" Default: \" \" \n \n " );
344+ use .append ("-out Out File Optional output file name.\n " );
345+ use .append (" Default: <p12 file>_nopass.p12\n \n " );
346+
347+ System .err .println (use );
348+ }
349+ }
350+ ///CLOVER:ON
0 commit comments