001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.jmeter.util;
020:
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.net.HttpURLConnection;
024: import java.security.KeyStore;
025: import java.security.Provider;
026: import java.security.Security;
027:
028: import javax.swing.JOptionPane;
029:
030: import org.apache.jmeter.gui.GuiPackage;
031: import org.apache.jmeter.util.keystore.JmeterKeyStore;
032: import org.apache.jorphan.logging.LoggingManager;
033: import org.apache.jorphan.util.JOrphanUtils;
034: import org.apache.log.Logger;
035:
036: /**
037: * The SSLManager handles the KeyStore information for JMeter. Basically, it
038: * handles all the logic for loading and initializing all the JSSE parameters
039: * and selecting the alias to authenticate against if it is available.
040: * SSLManager will try to automatically select the client certificate for you,
041: * but if it can't make a decision, it will pop open a dialog asking you for
042: * more information.
043: *
044: * TODO? - N.B. does not currently allow the selection of a client certificate.
045: *
046: * @author <a href="bloritsch@apache.org">Berin Loritsch</a>
047: */
048: public abstract class SSLManager {
049: private static final Logger log = LoggingManager
050: .getLoggerForClass();
051:
052: private static final String SSL_TRUST_STORE = "javax.net.ssl.trustStore";// $NON-NLS-1$
053:
054: private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; // $NON-NLS-1$
055:
056: public static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; // $NON-NLS-1$
057:
058: private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$
059:
060: /** Singleton instance of the manager */
061: private static SSLManager manager;
062:
063: private static boolean isSSLSupported = true;
064:
065: private static Provider sslProvider = null;
066:
067: /** Cache the KeyStore instance */
068: private JmeterKeyStore keyStore;
069:
070: /** Cache the TrustStore instance - null if no truststore name was provided */
071: private KeyStore trustStore = null;
072: // Have we yet tried to load the truststore?
073: private volatile boolean truststore_loaded = false;
074:
075: /** Have the password available */
076: protected String defaultpw = System.getProperty(KEY_STORE_PASSWORD);
077:
078: /**
079: * Resets the SSLManager so that we can create a new one with a new keystore
080: */
081: static public void reset() {
082: SSLManager.manager = null;
083: }
084:
085: public abstract void setContext(HttpURLConnection conn);
086:
087: /**
088: * Default implementation of setting the Provider
089: */
090: protected void setProvider(Provider provider) {
091: if (null != provider) {
092: Security.addProvider(provider);
093: }
094: }
095:
096: /**
097: * Opens and initializes the KeyStore. If the password for the KeyStore is
098: * not set, this method will prompt you to enter it. Unfortunately, there is
099: * no PasswordEntryField available from JOptionPane.
100: */
101: protected JmeterKeyStore getKeyStore() {
102: if (null == this .keyStore) {
103: String defaultName = JMeterUtils.getJMeterProperties()
104: .getProperty("user.home") // $NON-NLS-1$
105: + File.separator + ".keystore"; // $NON-NLS-1$
106: String fileName = System.getProperty(
107: JAVAX_NET_SSL_KEY_STORE, defaultName);
108: log.info("JmeterKeyStore Location: " + fileName);
109: try {
110: if (fileName.endsWith(".p12")
111: || fileName.endsWith(".P12")) { // $NON-NLS-1$ // $NON-NLS-2$
112: this .keyStore = JmeterKeyStore.getInstance(PKCS12);
113: log.info("KeyStore created OK, Type: PKCS 12");
114: System.setProperty("javax.net.ssl.keyStoreType",
115: PKCS12); // $NON-NLS-1$
116: } else {
117: this .keyStore = JmeterKeyStore.getInstance("JKS"); // $NON-NLS-1$
118: log.info("KeyStore created OK, Type: JKS");
119: }
120: } catch (Exception e) {
121: this .keyStore = null;
122: throw new RuntimeException(
123: "Could not create keystore: " + e.getMessage());
124: }
125:
126: FileInputStream fileInputStream = null;
127: try {
128: File initStore = new File(fileName);
129:
130: if (initStore.exists()) {
131: fileInputStream = new FileInputStream(initStore);
132: this .keyStore.load(fileInputStream, getPassword());
133: log
134: .info("Keystore loaded OK from file, found alias: "
135: + keyStore.getAlias());
136: } else {
137: log
138: .warn("Keystore file not found, loading empty keystore");
139: this .defaultpw = ""; // Ensure not null
140: this .keyStore.load(null, "");
141: }
142: } catch (Exception e) {
143: log
144: .error("Problem loading keystore: "
145: + e.getMessage());
146: } finally {
147: JOrphanUtils.closeQuietly(fileInputStream);
148: }
149:
150: log.debug("JmeterKeyStore type: "
151: + this .keyStore.getClass().toString());
152: }
153:
154: return this .keyStore;
155: }
156:
157: /*
158: * The password can be defined as a property; this dialogue is provided to allow it
159: * to be entered at run-time.
160: *
161: * However, this does not gain much, as the dialogue does not (yet) support hidden input ...
162: *
163: */
164: private String getPassword() {
165: String password = this .defaultpw;
166: if (null == password) {
167: if (null == defaultpw) {
168: this .defaultpw = System.getProperty(KEY_STORE_PASSWORD);
169:
170: if (null == defaultpw) {
171: synchronized (this ) {
172: this .defaultpw = JOptionPane
173: .showInputDialog(
174: GuiPackage.getInstance()
175: .getMainFrame(),
176: JMeterUtils
177: .getResString("ssl_pass_prompt"), // $NON-NLS-1$
178: JMeterUtils
179: .getResString("ssl_pass_title"), // $NON-NLS-1$
180: JOptionPane.QUESTION_MESSAGE);
181: System.setProperty(KEY_STORE_PASSWORD,
182: this .defaultpw);
183: }
184: }
185: }
186:
187: password = this .defaultpw;
188: System.setProperty(KEY_STORE_PASSWORD, password);
189: }
190: return password;
191: }
192:
193: /**
194: * Opens and initializes the TrustStore.
195: *
196: * There are 3 possibilities:
197: * - no truststore name provided, in which case the default Java truststore should be used
198: * - truststore name is provided, and loads OK
199: * - truststore name is provided, but is not found or does not load OK, in which case an empty
200: * truststore is created
201: *
202: * If the KeyStore object cannot be created, then this is currently treated the same
203: * as if no truststore name was provided.
204: *
205: * @return truststore
206: * - null: use Java truststore
207: * - otherwise, the truststore, which may be empty if the file could not be loaded.
208: *
209: */
210: protected KeyStore getTrustStore() {
211: if (!truststore_loaded) {
212:
213: truststore_loaded = true;// we've tried ...
214:
215: String fileName = System.getProperty(SSL_TRUST_STORE);
216: if (fileName == null) {
217: return null;
218: }
219: log.info("TrustStore Location: " + fileName);
220:
221: try {
222: this .trustStore = KeyStore.getInstance("JKS");
223: log.info("TrustStore created OK, Type: JKS");
224: } catch (Exception e) {
225: this .trustStore = null;
226: throw new RuntimeException(
227: "Problem creating truststore: "
228: + e.getMessage());
229: }
230:
231: FileInputStream fileInputStream = null;
232: try {
233: File initStore = new File(fileName);
234:
235: if (initStore.exists()) {
236: fileInputStream = new FileInputStream(initStore);
237: this .trustStore.load(fileInputStream, null);
238: log.info("Truststore loaded OK from file");
239: } else {
240: log
241: .info("Truststore file not found, loading empty truststore");
242: this .trustStore.load(null, null);
243: }
244: } catch (Exception e) {
245: throw new RuntimeException("Can't load TrustStore: "
246: + e.toString());
247: } finally {
248: JOrphanUtils.closeQuietly(fileInputStream);
249: }
250: }
251:
252: return this .trustStore;
253: }
254:
255: /**
256: * Protected Constructor to remove the possibility of directly instantiating
257: * this object. Create the SSLContext, and wrap all the X509KeyManagers with
258: * our X509KeyManager so that we can choose our alias.
259: */
260: protected SSLManager() {
261: }
262:
263: /**
264: * Static accessor for the SSLManager object. The SSLManager is a singleton.
265: */
266: public static final SSLManager getInstance() {
267: if (null == SSLManager.manager) {
268: SSLManager.manager = new JsseSSLManager(
269: SSLManager.sslProvider);
270: // if (SSLManager.isSSLSupported) {
271: // String classname = null;
272: // classname = "org.apache.jmeter.util.JsseSSLManager"; // $NON-NLS-1$
273: //
274: // try {
275: // Class clazz = Class.forName(classname);
276: // Constructor con = clazz.getConstructor(new Class[] { Provider.class });
277: // SSLManager.manager = (SSLManager) con.newInstance(new Object[] { SSLManager.sslProvider });
278: // } catch (Exception e) {
279: // log.error("Could not create SSLManager instance", e); // $NON-NLS-1$
280: // SSLManager.isSSLSupported = false;
281: // return null;
282: // }
283: // }
284: }
285:
286: return SSLManager.manager;
287: }
288:
289: /**
290: * Test whether SSL is supported or not.
291: */
292: public static final boolean isSSLSupported() {
293: return SSLManager.isSSLSupported;
294: }
295: }
|